Loading UserControl to the region of the Window on ComboBox change - c#

I have got ComboBox which is populated with collection of customTypes. On comboBox change, I would like to load/change content in the particular region so that it loads related data for the selected ComboBox item (it could be in the form of loading userControl or I i dont mind specifying DataTemplate for it).
It is similar to this question WPF Load Control question. But in this question, he is talking about individual DataTemplates in the actual Listbox, while I am talking about populating certain window region on ComboBox change.
I am using MVVM (and not PRISM) .Net 3.5.

U could use ContentControl which is the placeholder for the actual Content that is dynamically decided as per combobox selection.
The follwoing code is just for guidance
<Window ...>
<Window.Resources>
<MyView1 x:Key="MyView1" />
<MyView2 x:Key="MyView2" />
</Window.Resources>
...
<ComboBox x:Name="MyCombo">
<ComboBox.ItemsSource>
<sys:String>"MyView1"</sys:String>
<sys:String>"MyView2"</sys:String>
....
</ComboBox.ItemsSource>
</ComboBox>
...
<!-- This is where your region is loaded -->
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem,
ElementName=MyCombo}"
Value="MyView1">
<Setter Property="Content"
Value="{StaticResource MyView1}"
</DataTrigger>
<DataTrigger Binding="{Binding Path=SelectedItem,
ElementName=MyCombo}"
Value="MyView2">
<Setter Property="Content"
Value="{StaticResource MyView2}"
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Window>
The data loading can be part of the MyView1 and MyView2 user control's constructor or your main UI's data context view model.

As far as I understand the question is how to change underlying data being bound to UI and not a DataTemplate only.
You can use EventSetter which will be handled in code behind where you can switch DataContext for a region you've mentioned:
<ComboBox>
<ComboBox.Resources>
<Style TargetType="ComboBoxItem">
<EventSetter Event="Selector.SelectionChanged"
Handler="YourHandler"/>
</Style>
</ComboBox.Resources>
</ComboBox>
But from MVVM perspectives it could be not perfect solution so you can introduce your own ComboBox class wich is Command aware, see this SO post: WPF command support in ComboBox
In this way you can decouple logic from UI using Command.

Related

How to update a DataTemplate dynamically based on a object property?

I have a collection of objects that have different rendering options: They can be simple text, editable text, comboboxes, or event a mixed bag (like a comboBox where items are usually text but with images for specific values).
I managed to show everything correctly by using ContentTemplateSelector inside a ContentPresenter node inside the DataTemplate of the ListViewItem.ItemTemplate:
<ContentPresenter
Grid.Row="1"
Grid.Column="1"
Content="{Binding .}"
ContentTemplateSelector="{DynamicResource ResourceKey=RenderTemplateSelector}"/>
(note that everything is inside the DataTemplate of a ListView.ItemTemplate)
All is good until I change the value of said property and need to change the template, for instance going from a image to a text.
The VM does update correctly the values, but nothing happens in the GUI.
Looking around here there are a few methods (using a Converter (bound to which property??), defining a Style (i think is not relevant, since i must show different controls, not change properties of the same one), using an AttachedProperty (I don 't understand really well how attached properties work, but from what I saw I should have a ContentControl node, which I don't...) but nothing seems what I need.
So, a recap: I have a ListView that has a DataTemplate definded for each ListViewItem that contains another DataTemplate that needs to change based on a properti of the ListViewItem object. I managed to achieve this with a ContentTemplateSelector, but that is assigned at the beginning and then never changed.
You could try to replace the ContentPresenter with a ContentControl that has a Style with data triggers that sets the ContentTemplate, .e.g.:
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding YourProperty}" Value="Type1">
<Setter Property="ContentTemplate" Value="{StaticResource template1}" />
</DataTrigger>
<DataTrigger Binding="{Binding YourProperty}" Value="Type2">
<Setter Property="ContentTemplate" Value="{StaticResource template2}" />
</DataTrigger>
<!-- ... -->
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

Binding the Setter Value in a Style to the main model

i want to write a WPF-Application that shows different pages/scenes. So, I have a ViewModel (MainViewModel) that provides a list of scenes (SceneViewModel). Every scene has a name which can be accessed by a property SceneName.
I create the MainViewModel in Window.DataContext:
<Window.DataContext>
<!-- Declaratively create an instance of the main model -->
<models:MainViewModel />
</Window.DataContext>
I then want a menu-item that lists all scenes. When one of the menu-items is clicked, the scene should change. Therefor i created an Command in the MainViewMode: ChangeSceneCommand.
In XAML I want to create the menu-list in the following way:
<Menu Grid.Row="0">
<Menu.Resources>
<Style x:Key="SetSceneCommandItem" TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding SceneName}"/>
<Setter Property="Command" Value="{Binding SetSceneCommand}"/> <-- Here is the problem
<Setter Property="IsChecked" Value="False" />
</Style>
</Menu.Resources>
<MenuItem Header="Scenes" ItemsSource="{Binding Scenes}" <-- Scenes is a list with scenes^^
ItemContainerStyle="{StaticResource SetSceneCommandItem}" /> <-- Here the style is set
</Menu>
The Item-Header binds well but the "SetSceneCommand" can not be found, because wpf tries to find the "SetSceneCommand"-property in the SceneViewModel. How can i say wpf to access the model in the datacontext out of the style?
PS: You may have noticed that SetSceneCommand will need a scene as parameter to work, but I wanted to implement that later.
You can use RelativeSource. If SetSceneCommand is part of the same context used by Menu then it will look like this:
<Setter
Property="Command"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type Menu}}, Path=DataContext.SetSceneCommand}"/>
this tells Binding to go up the visual tree until Menu and take DataContext.SetSceneCommand from there

WPF Tab Data Template - Controls retain values when new tabs are created

I have a problem with the WPF tab control.
I have a TabControl, with the ItemsSource bound to an ObservableCollection. I created a data template for the header/content portion of the tabs. The content portion contains a custom control, with a bunch of labels and text boxes. For the text boxes that are editable when a new tab is created that data carries over and appears in the new tab. Not sure if it's a problem with my XAML or something in the view model. Here's my code for the XAML:
<UserControl.Resources>
<DataTemplate x:Key="TabItemHeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding AdFile.Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="TabItemContentTemplate">
<MyView:MyCustomControl/>
</DataTemplate>
<Style x:Key="TabItemContainerStyle" TargetType="TabItem">
<Setter Property="Header" Value="{Binding}"/>
<Setter Property="HeaderTemplate"
Value="{StaticResource TabItemHeaderTemplate}"/>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate"
Value="{StaticResource TabItemContentTemplate}"/>
</Style>
</UserControl.Resources>
<TabControl Grid.Row="3" ItemsSource="{Binding OpenedFiles}" x:Name="_myTabControl" SelectedItem="{Binding Path=CurrentDataControlViewModel, Mode=TwoWay}" SelectionChanged="TabControlSelectionChanged" ItemContainerStyle="{StaticResource TabItemContainerStyle}"/>
Not sure what other information I should provide. Maybe this is a common problem and I am just not setting something up correctly? Basically I just want to be able to create a new instance of the control for every tab...
Thanks in advance.
It sounds as though your ViewModel is a Singleton, being cloned, or you're trying to populate the new tab with the existing ViewModel.
If you're using MEF, remember to set the [PartCreationPolicy] attribute to NonShared.

Setting the DataContext of a control after a data triggered is fired

I have a view which contains a ContentControl which has its ContentTemplate changed dynamically depending on a boolean property within the view model using a data trigger.
<ContentControl>
<!-- MyFirstControl user control by default-->
<local:MyFirstControl/>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}"
Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- Different user control when trigger fired-->
<local:MySecondControl />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
The MySecondControl user control, which displays when the trigger is fired is intended to display a textblock, of which its Text property binds to a property also within the same view model.
I am obviously wrong, but my thinking was that the triggered control would inherit the same data context. Instead it is trying to use the MyFirstControl user control as its data context (I also receive this error: System.Windows.Data Error: 40 : BindingExpression path error:).
I have tried to explicitly state the data context of the triggered control with:
<local:MySecondControl DataContext="{Binding}"/>
However it is still using the default control (MyFirstControl) as its data context.
My question is, how do I force the triggered control to use the same data context as the view file it is within?
I am fairly new to the WPF scene so I hope this makes sense!
Thanks in advance.
There's a difference between Content and ContentTemplate.
Content is your actual content for the control, while ContentTemplate defines how to draw the Content
You are setting the Content property to MyFirstControl. Your trigger is changing the ContentTemplate property, so it is changing the way your Content (MyFirstControl) gets drawn so it is drawn using MySecondControl, however the Content itself is unchanged so the DataContext will still be your MyFirstControl.
You probably want to set the default ContentTemplate to MyFirstControl instead of the actual Content property.
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<!-- Set default ContentTemplate -->
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:MyFirstControl />
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}"
Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- Different user control when trigger fired-->
<local:MySecondControl />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Hope this will help:
<local:MySecondControl DataContext="{Binding Path=DataContext,
RelativeSource={RelativeSource TemplatedParent}}"/>

Switching ListBox ItemTemplate based on both item type and view option

I'm currently implementing a listbox in WPF that will have 2 alternative layouts for its items:
So far, I've done this using a DataTrigger to switch the ItemTemplate for the ListBox and it's working well:
<ListBox ItemsSource="{Binding Runs}" SelectedItem="{Binding SelectedRun}">
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="ItemTemplate" Value="{StaticResource tileTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowRunsAsIcons}" Value="True">
<Setter Property="ItemTemplate" Value="{StaticResource iconTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
However, the Runs collection to which the list is bound will also contain different types of object:
interface IRunItem
{
// ...
}
class CompletedRunItem : IRunItem
{
// ...
}
class PendingRunItem : IRunItem
{
// ...
}
Each of the object types should have its own 'tile' and 'icon' templates (making 4 templates in total). What's the best way of switching on these two properties to according to the boolean ShowRunsAsIcons and the type of the list item?
I've considered using a pair of DataTemplateSelector subclasses -- one to choose between tile templates based on item type, and one to choose between icon templates based on item type -- but this just feels horribly clunky. I feel as though I should be taking advantage of WPF's ability to choose the correct template based on the object's type, but in this instance, I don't see how to combine that with the list's different view options.
Any ideas of how to do this that's more in the spirit of WPF?
Thanks.
Although I'm not convinced it's the best answer, I've changed my approach to take advantage of WPF's automatic template selection. I now have 'top-level' data templates defined for each of my concrete data classes.
These data templates contain nothing but a ContentControl whose ContentTemplate property is set via a DataTrigger, binding to the data context's ShowRunsAsIcons property.
As an example, here's the keyless data template for PendingRunItem:
<DataTemplate DataType="{x:Type Common:PendingRunItem}">
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate" Value="{StaticResource pendingTileTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.ShowRunsAsIcons, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource pendingIconTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
The icon and tile representations for the relevant classes are then just regular data templates. And the ListBox no longer needs its Style property defined:
<ListBox ItemsSource="{Binding Runs}" SelectedItem="{Binding SelectedRun}"/>
I'd be interested to know people's thoughts on this approach and its benefits and drawbacks when compared to using a DataTemplateSelector or two.

Categories

Resources