Custom Control with 2 Content areas each with its own region

Topics: Prism v4 - WPF 4
Dec 19, 2011 at 4:48 PM

I have a custom control with 2 content areas. Here is the Style

<Style TargetType="{x:Type local:CustomControl1}">       

<Setter Property="Template">           

<Setter.Value>               

<ControlTemplate TargetType="{x:Type local:CustomControl1}">                   

<Grid>                       

<Grid.RowDefinitions>                           

<RowDefinition Height="Auto"/>                           

<RowDefinition />                       

</Grid.RowDefinitions>                       

<!-- Content presenter for hosting the content -->                       

<ContentPresenter x:Name="contentPresenter" Grid.Row="1"/>                       

<ContentPresenter x:Name="contentPresenter1" Content="{TemplateBinding ExpandedContent}" Visibility="Visible" Grid.Row="1"/>                   

</Grid>               

</ControlTemplate>           

</Setter.Value>       

</Setter>   

</Style>

 

Whenever I put a region in the second ExpandedContent, it never get's registered?? Where am I going wrong? Here is the code for the control:

 

    public class CustomControl1 : ContentControl   

{       

static CustomControl1()       

{           

DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));       

}

public Object ExpandedContent        {           

get { return (Boolean)this.GetValue(ExpandedContentProperty); }           

set { this.SetValue(ExpandedContentProperty, value); }       

}       

public static readonly DependencyProperty ExpandedContentProperty = DependencyProperty.Register("ExpandedContent", typeof(Object), typeof(CustomControl1));    }

Developer
Dec 19, 2011 at 8:28 PM

Hi,

Based on my understanding, your problem might be related to a known issue in Prism where if a Region is defined inside a template, the region is never registered in the RegionManager. This issue is described in the following work item, where you might find some useful approaches to workaround it:

If your problem is not related to this issue, it would be helpful if you could provide us with a repro-sample application portraying this problem so that we can help you further with it.

I hope you find this useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini

Dec 20, 2011 at 9:46 AM

I'm not entirely sure if it is that or not exactly, below is a link to the project. All I am wanting is the control to display two different content areas. It is two content presenters. The first one works but my additional content doesn't.

https://skydrive.live.com/#cid=A53184DCECF800FE&id=A53184DCECF800FE%21125

Developer
Dec 20, 2011 at 8:05 PM
Edited Dec 20, 2011 at 8:06 PM

Hi,

We have checked your repro-sample application and found that when the ContentControl containing the region "ExpandedRegion" is added to your custom control through the ExpandedContent property, the corresponding RegionManager is not attached as a dependency property in the ContentControl. Based on my understanding, it might be possible that as the aforementioned ContentControl is added through the template, it might not have a parent from which the RegionManagerRegistrationBehavior can obtain the corresponding RegionManager; therefore, the region is never registered.

As the view model implements the IRegionManagerAware interface, which allows it to know the RegionManager of its view (through the RegionManagerAwareBehavior), you can attach the RegionManager as a dependency property in the ContentControl doing something like this:

<custom:CustomControl1.ExpandedContent>
    <Grid>
        <ContentControl cal:RegionManager.RegionName="ExpandedRegion" cal:RegionManager.RegionManager="{Binding RegionManager}">
        </ContentControl>
    </Grid>
</custom:CustomControl1.ExpandedContent>

You can check that the "ExpandedRegion" was correctly registered by, for example, adding a view when clicking the Navigate button of the view, adding this code in the Navigate method of the view model:

void Navigate()
{
    this.regionManager.Regions["ExpandedRegion"].Add("Some text");
}

Also, you can find more information about the IRegionManagerAware interface and the RegionManagerAwareBehavior it uses in the following blog post:

I hope you find this useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini

Dec 21, 2011 at 9:47 AM

So presumably because in my actual control I am using, I have the ExpandedContent collapsed, the region only get's added when it is made visible? Because of the binding?

It feels like I haven't defined the ExpandedContent property correctly. If I make CustomControl1 inherit from Control instead of ContentControl and only use my ExpandedContent property it still doesn't work, so what am I doing wrong?

Developer
Dec 21, 2011 at 5:14 PM

Hi,

Based on my understanding, as Damian Cherubini mentioned above, the reason that the region is not getting registered might be caused by the fact that the ContentControl is being added through the template, and might not have a parent from which the RegionManagerRegistrationBehavior can obtain the corresponding RegionManager to achieve the region registration, hence it doesn't seem to be related to the "visibility" of the ExpandedContent property. As a possible approach, this can be solved binding the RegionManager to the ContentControl in the ExpandedContent property as shown in the code snippet above.

Also as an alternative to binding the RegionManager to the ContentControl you could try using the View Discovery approach. In this approach you set up a relationship in the RegionViewRegistry between a region's name and the type of a view, without querying the RegionManager. When the region is available it automatically instantiates and loads the corresponding views. Take into account that this approach may be appropriate depending the requirements of your scenario as you will not have explicit control over when the views that correspond to a region are loaded and displayed.

On the other hand, regarding how to add DependencyProperties you might find the following links useful:

I hope you find this handy,

Agustin Adami
http://blogs.southworks.net/aadami


Dec 23, 2011 at 10:26 AM

Well trying the approach suggested with the binding, it doesn't seem to be registered when I first navigate to the ViewModel like other regions. So causes a few timing issues when attempting to navigate. I just presumed it may not get added until the region was made visible.

 

To simplify the issue I wanted to make my own Content Control as I don't understand why in theory just adding another content property shouldn't work? So I created a my control inheriting from Control and added my own content property, but this still doesn't work. It should work as the standard content control works?? How would I get this to work? It seems like the standard control works so this example should work as well?? I shouldn't have to add this workaround which messes up timings.

 

I have uploaded my example to: https://skydrive.live.com/#cid=A53184DCECF800FE&id=A53184DCECF800FE%21125 

I don't see why this wouldn't work I am trying to replicate Microsoft's standard content control? There must be something else that is required.

Dec 23, 2011 at 10:27 AM

Merry Christmas btw!

Developer
Dec 26, 2011 at 7:47 PM

Hi,

As explained before, this problems seems to occur because the content added to the ExpandedContentProperty is not part of the logical tree of the control, and thus, it doesn't have a logical parent from which the RegionManagerRegistrationBehavior can obtain the corresponding RegionManager. If the previous approaches are not fit for your scenario, you could also try to add the content of the ExpandedContentProperty to the logical tree of your control manually. This can be done overriding the OnPropertyChanged method of the control to "listen" to any changes on the ExpandedContentPropert, doing something similar to this:

public class CustomControl1 : ContentControl
    {
        
        static CustomControl1()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
        }

        public static readonly DependencyProperty ExpandedContentProperty = DependencyProperty.Register("ExpandedContent", typeof(object), typeof(CustomControl1));

        public object ExpandedContent
        {
            get { return (object)GetValue(ExpandedContentProperty); }
            set { SetValue(ExpandedContentProperty, value); }
        }

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);

            // If ExpandedContentProperty changed,
            if (e.Property.Name == ExpandedContentProperty.Name)
            {
                // and its value is diferent than before,
                if(e.NewValue != e.OldValue)
                {
                    // we remove the old element from the Logical Tree
                    if (e.OldValue is FrameworkElement)
                    {
                        this.RemoveLogicalChild(e.OldValue);
                        BindingOperations.ClearBinding(e.OldValue as DependencyObject, FrameworkElement.DataContextProperty); 
                    }
                    
                    // and if the new value is a FrameworkElement...
                    if (e.NewValue is FrameworkElement)
                    {
                        // we add the new value to the Logical Tree.
                        this.AddLogicalChild(e.NewValue);

                        // We also set the DataContext of the new value to be the same that the DataContext of this control.
                        BindingOperations.SetBinding(e.NewValue as DependencyObject, FrameworkElement.DataContextProperty, new Binding("DataContext") { Source = this });
                    }
                }
            }
        }
    }

Then, the ContentTemplate for the control can be simply defined as this:

<ControlTemplate TargetType="{x:Type local:CustomControl1}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition />
        </Grid.RowDefinitions>

        <!-- Content presenter for hosting the content -->
        <ContentPresenter x:Name="contentPresenter" Content="{TemplateBinding Content}"  Grid.Row="0"/>
        <ContentPresenter x:Name="contentPresenter1" Content="{TemplateBinding ExpandedContent}" Grid.Row="1"/>
    </Grid>
</ControlTemplate>

And used as this:

<custom:CustomControl1>
    <custom:CustomControl1.Content>
        <Grid>
            <ContentControl cal:RegionManager.RegionName="SmallRegion">
            </ContentControl>
        </Grid>
    </custom:CustomControl1.Content>
    <custom:CustomControl1.ExpandedContent>
        <StackPanel>
            <ContentControl cal:RegionManager.RegionName="ExpandedRegion">
            </ContentControl>
            <Button Content="Navigate" Command="{Binding NavigateCommand}"/>
        </StackPanel>
    </custom:CustomControl1.ExpandedContent>
</custom:CustomControl1>
As a side note and based on my understanding, I think that a ContentControl is supposed to have only one "content." If more that one content is required, it might make sense to inherit from a ItemsControl instead, and wrap the items in the Items collection through properties, creating the look of a control with more than one content property. However, this depends mostly of your personal preferences and the requirements of your scenario.

I hope you find the above workaround useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini

Jan 17, 2012 at 8:50 AM

Thank you I have added the OnPropertyChanged method to listen for changes. Will see how this goes as it seems the easiest solution at present.