Binding the Setter Value in a Style to the main model - c#

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

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>

Is it possible to specify the type of the data context for a Style?

I'm working on a WPF application that is using styles and templates to dynamically build a ribbon menu, here are the relevant snippets:
<RibbonTab x:Name="HomeTab" Header="Home" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource GroupStyle}" ItemsSource="{Binding Groups}"/>
<Style TargetType="RibbonGroup" x:Key="GroupStyle">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="ItemsSource" Value="{Binding Controls}"/>
<Setter Property="ItemTemplate" Value="{StaticResource RibbonButtonTemplate}"/>
</Style>
<DataTemplate x:Key="RibbonButtonTemplate" DataType="{x:Type enums:RibbonControlConfig}">
<RibbonButton Label="{Binding Header}" LargeImageSource="{Binding Image}" Command="{Binding Command}"/>
</DataTemplate>
So now Resharper is complaining about the bindings within the GroupStyle since they are actually elements of a child property of the view model, as can be seen by the ItemSource binding on the RibbonTab.
This does of course function just fine, I would just prefer to have both no complaints from ReSharper and access to accurate Intellisense, so is there anyway to tell the style what it's context is?
Side note: I did first try to do this setup by using nested data templates but I wasn't able to get anything passed the first level template to work.
No, it's not possible to specify the DataContext type in a style. In general, you shouldn't use bindings in a style anyway, because a style is about appearance, not about data. It's especially important to follow this rule if you plan to make your control reusable: you can't make assumptions about what it will be bound to.

Using commands with MenuItem's in WPF

So I have this MenuItem which is using a list of CultureInfos as an itemsource.
What I'm trying to do is to fire a function when a CultureInfo is clicked/selected, which is supposed to change the localization of the application to the selected cultureinfo.
I can safely say that the function is working as it should.
After researching and trying a few examples, this is what I ended up with, which unfortunately doesn't work:
<MenuItem Header="{lex:LocText MenuLanguages}" ItemsSource="{Binding LanguageList}" DisplayMemberPath="Name">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Command" Value="{Binding SetLanguage}" />
<Setter Property="MenuItem.CommandParameter" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
The command:
private ICommand _SetLanguage;
public ICommand SetLanguage
{
get
{
if (_SetLanguage == null)
_SetLanguage = new RelayCommand(ChangeLanguage);
return _SetLanguage;
}
}
public void ChangeLanguage(object langChosen)
{
LocalizeDictionary.Instance.Culture = CultureInfo.GetCultureInfo(langChosen.ToString());
}
CultureInfo.GetCultureInfo() takes string as a parameter.
The itemsource is basically an ObservableCollection<CultureInfo>.
My question is what could be wrong with the above code? I have been trying quite a few 'solutions' but nothing really worked...
When I select an item form the list nothing happens at all.
*I also tried to set a breakpoint on the ChangeLanguage method, which brought me to a conclusion that the method never even fires.
Using caliburn.micro and WPFLocalizationExtension extension.
If it doesn't work, then the binding is wrong. Check out for binding errors in debug window.
You need to use relativesource, since binding directly wont do what you think will do.
<MenuItem Header="_Recent files" ItemsSource="{Binding RecentFiles, Converter={StaticResource RecentFilesToListOfStringsConverter}, Mode=OneWay}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding DataContext.ImportRecentItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type MenuItem}, AncestorLevel=1}}" />
<Setter Property="CommandParameter" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
A Menu has a different LogicalTree than the rest of the Markup. For this Reason you need to search for the right DataContext first as Erti-Chris suggested.
Also every DataTemplate has the DataContext of the Type T which is part of the List of the bound ItemsSource.

Reuse context menu

I have created a context menu that I (at the moment) use for some items in my treeview. For that I have created a TreeItem class that holds all the relevant information like header, icon, children, execute target, etc. This is what it looks like:
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"
Visibility="{Binding ShowContextMenu}"
ItemsSource="{Binding ContextMenu}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" />
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Execute}" />
<Setter Property="Icon"
Value="{StaticResource cmIcon}" />
<Setter Property="ToolTip"
Value="{Binding ToolTip}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Execute}" />
<Setter Property="Icon"
Value="{StaticResource cmIcon}" />
<Setter Property="ToolTip"
Value="{Binding ToolTip}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
When I used the context menu only in the tree view, it was attached to the TextBlock in the ItemTemplate. But now I want to use the same context menu for a different control. As I don't want to copy the same code to a different location and maintain it multiple times, I want to reuse it as template. I tried 2 things:
I put the context menu in the resources of the user control (just for testing) and call it like this: <TextBlock Text="{Binding Header}" ContextMenu="{StaticResource myContextMenu}">. It will be displayed, but not be closed and not move. Also this is not really helpful anyway as I want to use the context menu on a different user control.
Then I put the context menu inside a control template in the App.xaml: <ControlTemplate x:Key="TreeContextMenu" TargetType="ContextMenu">. And I call it like this:
<TextBlock.ContextMenu>
<ContextMenu Template="{StaticResource TreeContextMenu}"/>
</TextBlock.ContextMenu>
The program starts, but when I want to open the context menu, I get an exception: 'ContextMenu' cannot have a logical or visual parent.
I have tried to google for a solution, but couldn't find anything helpful.
You are trying to create a context menu inside a context menu. Remove the ControlTemplate tag from the App.xaml and move the x:Key attribute directly to the ContextMenu tag.
Also, delete the TextBlock.ContextMenu and add ContextMenu="{StaticResource TreeContextMenu}" attribute to the TextBlocktag.

Loading UserControl to the region of the Window on ComboBox change

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.

Categories

Resources