Master page but for Regions? - wpf

I have a RibbonBar with four buttons, each of which selects a different view and injects it into the MainRegion. Right now, it's working well but each of the four views have a few controls in common e.g. Displaying the username, date/time, search box etc. As of now, I've made separate views but I would like to know if there is a way of implementing a Master Page/UserControl so that I don't have to repeat code.
So the design, right now, is like so:
Shell
<Window>
<Grid>
<DockPanel>
<RibbonBar Regions:RegionManager.RegionName="ToolbarRegion">
</RibbonBar>
<ContentControl Regions:RegionManager.RegionName="MainRegion">
</ContentControl>
</DockPanel>
</Grid>
</Window>
The ToolbarRegion contains the RibbonBar with the four buttons.
Each Button will inject a view into the MainRegion. Each of these views has some common controls (much like a Master Page in ASP.Net), but the content of each view is different.
There are ways to work around this (I can't change the Shell design) but I was wondering if there was a better, more elegant way to do this.

I like using the Prism framework, however I feel that their Regions should only be used for Application Layout (MenuRegion, NavigationRegion, ContentRegion, etc), and not Navigation. Using them for Navigation means letting the View control the application flow, and I feel that is a job for the ViewModels.
My preferred method for displaying changing content is with DataTemplates and ContentControls
To create something like you outlined, I would have a parent ViewModel which contains
ObservableCollection<IPageViewModel> PageViewModels
IPageViewModel SelectedPageViewModel
The area I wanted to display the dynamic content would use a ContentControl such as this:
<ContentControl Content="{Binding SelectedPage" />
And DataTemplates would be used to tell WPF how to draw each section
<DataTemplate TargetType="{x:Type local:Page1ViewModel}">
<local:Page1View />
</DataTemplate>
<DataTemplate TargetType="{x:Type local:Page2ViewModel}">
<local:Page2View />
</DataTemplate>
<DataTemplate TargetType="{x:Type local:Page3ViewModel}">
<local:Page3View />
</DataTemplate>
I have not used a RibbonBar before, however it sounds like it should allow for an ItemsSource so your final XAML should probably look something similar to this:
<Window>
<Grid>
<DockPanel>
<RibbonBar ItemsSource="{Binding PageViewModels}"
SelectedItem="{Binding SelectedPageViewModel}"
DockPanel.Dock="Top" ...>
</RibbonBar>
<StackPanel>
<Grid>
... Generic Content
</Grid>
<ContentControl Content="{Binding SelectedPageViewModel}">
<ContentControl.Resources>
<DataTemplate TargetType="{x:Type local:Page1ViewModel}">
<local:Page1View />
</DataTemplate>
<DataTemplate TargetType="{x:Type local:Page2ViewModel}">
<local:Page2View />
</DataTemplate>
<DataTemplate TargetType="{x:Type local:Page3ViewModel}">
<local:Page3View />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</StackPanel>
</DockPanel>
</Grid>
</Window>

Content of your Shell.xaml:
<Window ...>
<StackPanel>
<Grid>
<!-- Your shared content -->
</Grid>
<ContentControl prism:RegionManager.RegionName="MainRegion"/>
</StackPanel>
</Window>

Related

UI object, on samelevel of XAML Tree, as CommandParameter

I have an XAML tree as follows:
<Window>
<Grid>
<DockPanel>
<DataGrid>
<DataGrid.Resources>
<CheckBox Command="{Binding Command}" CommandParameter="??" />
</DataGrid.Resources>
</DataGrid>
<StackPanel>
<ChartLegend>
</ChartLegend>
<DataChart>
</DataChart>
</stackPanel>
</DockPanel>
</Grid>
</Window>
I want to have DataChart object as CommandParameter on ViewModel from a Command on DataGrid.
My Findings:
I'm getting DockPanel object as CommandParameter, then I have to apply method FindName("") to get the DataChart. And do further modifications.
But I want the DataChart object directly, to avoid TypeCasting or searching down the Tree.
You can keep datachart as a named resource in your DockPanel resources and use static resource binding to command parameter. Then use ContentControl to host it.
like this...
<DockPanel>
<DockPanel.Resources>
<DataChart x:Key="MyDataChart">
</DataChart>
</DockPanel.Resources>
<DataGrid>
<DataGrid.Resources>
<CheckBox
Command="{Binding Command}"
CommandParameter="{StaticResource MyDataChart}" />
</DataGrid.Resources>
</DataGrid>
<StackPanel>
<ChartLegend>
</ChartLegend>
<ContentControl Content="{StaticResource MyDataChart}"/>
</stackPanel>
</DockPanel>
Hoping that you wont use same MyDataChart to host to another area (as that would result in "visual tree parent disconnect" error).
Although I must ask you this... why is there a lonely CheckBox in your DataGrid resources?
Also your's and mine solution breaks MVVM because we are supplying a UI control (Chart control) to a View Model.

WPF Implicit selection of template using DataTemplate, but outside of 'List'

In my project, I have TreeView, which contains a tree of objects of various types (all subclassed from the same superclass).
To the right of my TreeView I would like to have a "panel" (at the moment I just have a Grid) which displays information about the object currently selected in the tree. I want to use DataTemplate, as in the second example on this page, to adapt the layout & content of my "panel" based on the subclass type; however, I cannot find a suitable container (as I don't want a list control - I want to change my display for one item based on the selection in the treeview).
This question asks the same thing but I don't think the answer is suitable for me because I want the template to change dynamically depending on the type.
I.e. I was hoping for something like:
<[A Suitable Container] Margin="189,39,12,12" DataContext="{Binding ElementName=treeView1, Path=SelectedItem}">
<DataTemplate DataType="{x:Type local:subclass1}">
<Grid>
<!-- subclass1 specific stuff -->
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:subclass2}">
<Grid>
<!-- subclass2 specific stuff -->
</Grid>
</DataTemplate>
</[A Suitable Container]>
Use a ContentControl
<ContentControl Content="{Binding ElementName=treeView1, Path=SelectedItem}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:ViewModelA}">
<local:ViewA />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelB}">
<local:ViewB />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>

ListBox templating: how to create a common template but still change content based on item

I've been templating ListBoxes for sometime in WPF, but I was wondering if there was a way to have a template for the ListBoxItem that would apply to all the items in the ListBox, but also have a ItemTemplateSelector to alter the contents of the containers.
I have a list of strings and images and I want to display them uniquely such that the image displays with a frame and strings display in a textbox to be edited. I made an ItemTemplateSelector and select the template based on the type. However I want to add some controls, like a button to delete and a checkbox to display selection to both templates.
I know I can add both objects to both templates for strings or images, but I want it to be able to scale and not have to added each time I add a template. Any thoughts?
You could use the ItemContainerStyle to override the Template of the ListBoxItems (probably not something i would do).
Alternatively you can define a ItemTemplate which frames your Templates by using a ContentControl, e.g.
<ListBox ItemsSource="{Binding Data}">
<ListBox.Resources>
<!-- The frame that is applied to all items -->
<ControlTemplate x:Key="commonFrameTemplate" TargetType="{x:Type ContentControl}">
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="5" Padding="5">
<StackPanel>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=IsSelected}"/>
<ContentPresenter /> <!-- Where the individual templates end up -->
<Button Content="Delete"/>
</StackPanel>
</Border>
</ControlTemplate>
<!-- Define templates without using a x:Key but setting the DataType,
the template will automatically be applied, no need for a
template-selector -->
<DataTemplate DataType="{x:Type local:Employee}">
<TextBlock Text="{Binding Name}" Foreground="Red"/>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<!-- By setting the content to {Binding} the templating is delegated
in a way, if you must use a selector, define one here as
ContentTemplateSelector -->
<ContentControl Template="{StaticResource commonFrameTemplate}"
Content="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF - Databind to a StackPanel using DataTemplates

I've modified my question since it has changed focus when trying things out.
I narrowed the problem down to the following...
I try to bind the selected Item of a TreeView to a StackPanel (or some other container that can hold User Controls). This container will then display a UserControl, depending on the type of the selected item.
Here is the xaml of the StackPanel (both treeview and stackpanel are in the same window ==> different grid column)
<StackPanel Grid.Column="2" MinWidth="500" DataContext="{Binding ElementName=myTree, Path=SelectedItem, Mode=OneWay}">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type mvTypes:MyTypeA}">
<controls:UserControlA DataContext="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type mvTypes:MyTypeB}">
<controls:UserControlB DataContext="{Binding}" />
</DataTemplate>
</StackPanel.Resources>
</StackPanel>
When I place a user control directly under the stackpanel (not in the resources), it displays it with the selected object as their datacontext.
Idem if I place a TextBox in it, it will show the correct type of the selected item.
<TextBox Name="textBox1" Text="{Binding}" />
For some reason, placing it within a DataTemplate (even without setting the DataType) results in nothing to display.
Any sugestions. I'm thinking that maybe a StackPanel is not the right control for this, though I can't seem to find other controls that look suitable as containers like this.
Thanks in advance.
Replace the StackPanel in your example with ContentPresenter and instead of DataContext set the Content property. That should work.
Although you have set the Binding on the second custom control, are you setting the DataContext, as the binding is the route to the information and the DataContext is the information it applies this binding information to.
Andrew
You can create a UserControl to display the TreeView and the selection info on the right, all in one. It saves you from creating any custom control. A custom control is basically unnecessary since you do not create anything which didn't exist before.
<UserControl x:Class="NameSpace.SelectionView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="namespace.Controls"
Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TreeView Name="customTree">
<!--Items go here-->
</TreeView>
<StackPanel Grid.Column="1" MinWidth="50" DataContext="{Binding ElementName=customTree, Path=SelectedItem, Mode=OneWay}">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type StylingTest:CustomViewModelA}">
<controls:CustomADetailsControl />
</DataTemplate>
<DataTemplate DataType="{x:Type StylingTest:CustomViewModelB}">
<controls:CustomBDetailsControl />
</DataTemplate>
</StackPanel.Resources>
<TextBlock Text="{Binding}"/>
</StackPanel>
</Grid>
</UserControl>
Any other custom behaviour, I'm sure you could create or set in styles/templates here.
Also, you might find one of my other answers useful.
Good luck with wpf, cheers.

Is there a way to group RadioButtons generated from the ItemTemplate of an ItemsControl

<DataTemplate x:Key="Genre_DataTemplate">
<RadioButton GroupName="One" Content="{Binding...
</DataTemplate>
Above code is the ItemTemplate of my ItemsControl, I want all the Radiobuttons instantiated should behave as if it is in a group, I know the reason because the generated RadioButtons are not adjacent in the visualtree.
Any solution or workaround to group them together?. GroupName property also doesn't have any effect here.
[Update] I am trying this in Silverlight
The problem is that the RadioButton.GroupName behavior depends on the logical tree to find a common ancestor and effectively scope it's use to that part of the tree, but silverlight's ItemsControl doesn't maintain the logical tree. This means, in your example, the RadioButton's Parent property is always null
I built a simple attached behavior to fix this. It is available here: http://www.dragonshed.org/blog/2009/03/08/radiobuttons-in-a-datatemplate-in-silverlight/
I think the problem is somewhere else in the control tree. Can you post more details?
Here is a sample xaml code that works as expected:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.Resources>
<XmlDataProvider x:Key="flickrdata" Source="http://api.flickr.com/services/feeds/photos_public.gne?tags=flower&lang=en-us&format=rss_200">
<XmlDataProvider.XmlNamespaceManager>
<XmlNamespaceMappingCollection>
<XmlNamespaceMapping Prefix="media" Uri="http://search.yahoo.com/mrss/"/>
</XmlNamespaceMappingCollection>
</XmlDataProvider.XmlNamespaceManager>
</XmlDataProvider>
<DataTemplate x:Key="itemTemplate">
<RadioButton GroupName="One">
<Image Width="75" Height="75" Source="{Binding Mode=OneWay, XPath=media:thumbnail/#url}"/>
</RadioButton>
</DataTemplate>
<ControlTemplate x:Key="controlTemplate" TargetType="{x:Type ItemsControl}">
<WrapPanel IsItemsHost="True" Orientation="Horizontal"/>
</ControlTemplate>
</Grid.Resources>
<ItemsControl
Width="375"
ItemsSource="{Binding Mode=Default, Source={StaticResource flickrdata}, XPath=/rss/channel/item}"
ItemTemplate="{StaticResource itemTemplate}"
Template="{StaticResource controlTemplate}">
</ItemsControl>
</Grid>
</Page>
P.S.: In order grouping to work elements radio buttons should have same parent (as they usually have when generated from ItemsControl)

Resources