How to databind IsSelected in Style when using ItemTemplate on TreeView - c#

I'm attempting to make a TreeView which uses an ItemTemplate, and also is bound to a view model which has a IsSelected property.
I'm not exactly sure why this isn't working, so I apologize if the question seems vague.
I've also reduced the code to what I think is the problem, as my app has a lot more XAML, so I hope what I've included is sufficient.
Here's how I define my DataTemplate:
<DataTemplate x:Key="ElementDataTemplate">
<Label Content="{Binding Path=DisplayText}"></Label>
</DataTemplate>
Here's how I use it:
<TreeView ItemsSource="{Binding Elements}"
ItemTemplate="{DynamicResource ElementDataTemplate}">
<TreeView.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected"
Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.Resources>
</TreeView>
My ViewModel has DisplayText and IsSelected properties.
I know that I'm binding correctly to the ViewModel because I am seeing the same number of items as in my Elements property of my data context, and my Label's Coutent is properly being set to DisplayText- I've verified this through breakpoints on the getter and visually when I run the app.
However, the IsSelected property's getter on the ViewModel never gets called (never hits a breakpoint), so it's clear I've messed up something on the binding of the IsSelected property.
I should point out that when I run the application I can select items with the mouse and they reflect the selection visually, so the TreeView item itself is getting selected, it's just not binding to the ViewModel's IsSelected property. Any help would be much appreciated!

Ahh, I'm foolish! I spent forever trying to figure out what it was, came here after a few hours, then finally figured it out:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
I was using "ListBoxItem" instead of "TreeViewItem". Copy/paste got the best of me.

Related

Multi-stage binding from ViewModel to Custom-Control to controls on ControlTemplate not working

I have a MVVM project with a View and a ViewModel in its DataContext.
In this project I have a class ComboBoxCustom which inherits from ComboBox. I define some additional functionality in my ComboBoxCustom class.
To this ComboBoxCustom class I assign a control template to define its appearance.
The (simplified) style defining the (simplified) control template looks like:
<Style TargetType="{x:Type lib:ComboBoxCustom}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type lib:ComboBoxCustom}">
<StackPanel>
<TextBlock Text="{TemplateBinding TextPropertyInComboBoxCustom}"/>
<ComboBox DataContext="{TemplateBinding DataContext}"
ItemsSource="{TemplateBinding ItemsSource}"
DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
SelectedValuePath="{TemplateBinding SelectedValuePath}"
SelectedValue="{TemplateBinding SelectedValue}"
SelectedItem="{TemplateBinding SelectedItem}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Which resides in a ResourceDictionary. The real control template has some additional features which are left out since they are not relevant for the question.
I use this ComboBoxCustom control in my View using:
<lib:ComboBoxCustom ItemsSource="{Binding MyObservableCollectionOfMyObjects}"
TextPropertyInComboBoxCustom="MyText"
DisplayMemberPath="MyDescription"
SelectedValuePath="MyValue"
SelectedItem="{Binding SelectedMyObject, Mode=TwoWay}"/>
The view is ok, and all items get loaded in the ComboBox which I can select.
The problem is that when I select a different item in the ComboBox, the property SelectedMyObject in my ViewModel does not get updated and consequently its setter is not called. Therefore, the (correct) information about the selected object is not available in my ViewModel.
When I use <ComboBox .../> (without the TextPropertyInComboBoxCustom property) instead of <lib:ComboBoxCustom .../> everything works just fine but then I don't have the additional functionality defined in ComboBoxMessage which I need.
Can anyone tell me what is wrong and how to fix this issue so I can use ComboBoxMessage in my view? Preferably without breaking the MVVM pattern.
Thank you!
Thanx to ASh's comment and information in this post.
The problem is that the TemplateBinding is one way. Therefore, all information from the ViewModel can get into the controls in the template. But not the other way around.
The solution is to specify a normal binding as:
SelectedItem ="{Binding SelectedItem, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Whichs does about the same as a TemplateBinding but is two way.
The control template has become:
<Style TargetType="{x:Type lib:ComboBoxCustom}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type lib:ComboBoxCustom}">
<StackPanel>
<TextBlock Text="{TemplateBinding TextPropertyInComboBoxCustom}"/>
<ComboBox DataContext="{TemplateBinding DataContext}"
ItemsSource="{TemplateBinding ItemsSource}"
DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
SelectedValuePath="{TemplateBinding SelectedValuePath}"
SelectedValue="{TemplateBinding SelectedValue}"
SelectedItem ="{Binding SelectedItem, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I am not sure about the SelectedValue property though. With the template like this, it works both when I use the SelectedValue property or the SelectedItem property.
The Mode=TwoWay option can be omitted in the view since the default binding mode for SelectedItem is already two way. The view line becomes:
<lib:ComboBoxCustom ItemsSource="{Binding MyObservableCollectionOfMyObjects}"
TextPropertyInComboBoxCustom="MyText"
DisplayMemberPath="MyDescription"
SelectedValuePath="MyValue"
SelectedItem="{Binding SelectedMyObject}"/>
Bind SelectedValue to the property in ViewModel
SelectedValue="{Binding SelectedMyObject, Mode=TwoWay}"
in your lib:ComboBoxCustom

Dynamically set UserControl as Listbox DataTemplate body

I have the following setup:
<ListBox ItemSource="{Binding Targets}">
<ListBox.ItemTemplate>
<DataTemplate>
<view:ViewName />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What I am trying to accomplish is to dynamically decide which view to use at runtime, based on a property within the DataContext of the ListBox. In simple terms, I want to replace <view:ViewName> with a data binding that returns the proper view.
I use MEF to provide plug-ins for my app that may need to provide a custom view to display the items when appropriate. At design time I won't know all the possible view types (they may be dynamically loaded from a DLL) so a simple DataTemplateSelector won't do.
I have researched solutions but have come up empty.
Since you want to change templates based on a bound value, you can use a DataTrigger to determine the ContentTemplate of the ListBoxItem
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}"/>
<Style.Triggers>
<DataTrigger Property="{Binding SomeProperty}" Value="A">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateA}"/>
</DataTrigger>
</Style.Triggers>
</Style>
I find this better than using a DataTemplateSelector because it gets re-evaluated if the bound property changes, while a DataTemplateSelector does not.
If you want to change templates based on an object type, you can use Implicit DataTemplates. These are DataTemplates that define a DataType, but no x:Key, and they will be used anytime WPF tries to draw an object of the specified type.
For example, if you had this template defined in your <X.Resources> somewhere
<DataTemplate DataType="{x:Type models:ActionA}">
<views:ActionAView />
</DataTemplate>
you could then insert your Model object directly into the UI and WPF would draw it using the template you specified
<ContentControl Content="{Binding SomeIActionObject}" />
<ItemsControl ItemsSource="{Binding CollectionOfIActionObjects}" />
Update
You mentioned that you would be allowing users to create modules with additional Templates that get imported using MEF, so in that case you would probably be better off using an IValueConverter that look up the matching template within Application.Resources
For example, if the bound value equals "A", then the converter might search Application.Resources for a template named "TemplateA" and return it to the binding
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate"
Value="{Binding SomeProperty,
Converter={StaticResource MyTemplateConverter}}"/>
</Style>
Using the DataTemplateManager from this post You can do something like:
DataTemplateManager.RegisterDataTemplate<ViewModelType1, ViewType1>();
DataTemplateManager.RegisterDataTemplate<ViewModelType2, ViewType2>();
DataTemplateManager.RegisterDataTemplate<ViewModelType3, ViewType3>();
then you would remove the ItemTemplate from the ListBox:
<ListBox ItemSource="{Binding Targets}"/>
and in the ListBox ViewModel you could:
public void AddTargets()
{
Targets.Add(new ViewModelType1());
Targets.Add(new ViewModelType2());
Targets.Add(new ViewModelType3());
}
Then, each DataTemplate will be automatically used by WPF to render each corresponding ViewModel.
Also note that you can call DataTemplateManager.RegisterDataTemplate() at any time before showing the ListBox, so you can theoretically do that when loading the MEF parts.
Edit:
Based on your comment, you could create a single DataTemplate with a ContentPresenter to display the selected View according to a property in the ViewModel:
<DataTemplate DataType="{x:Type local:TargetViewModel}">
<ContentPresenter x:Name="MainContentPresenter" Content="{Binding}" ContentTemplate="{Binding YourProperty, Converter=SomeConverter}"/>
and inside the SomeConverter you should use the same technique as demonstrated in the post, to dynamically generate a DataTemplate.

Disable particular item in the ListBox

I bind list of objects to my ListBox in WindowsPhone application. This business object has one boolean property. Based on the Boolean property i need to set the IsEnabled property in the ListBoxItem.
Is there any way to achieve this in WindowsPhone ListBox ?
There're few ways to achieve this:
You can add the ListBoxItem by C# code and set the property properly.
In Xaml, inside the list box, place the following:
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="IsEnabled" Value="{Binding Content.IsEnabled, RelativeSource={RelativeSource Self}}"/>
</Style>
</ListBox.Resources>
Here I assume that the property name in your model object is IsEnabled, change it according to its real name.

Add List to c# TreeView

I have an object MyResult that contains a list "List MyItems".
I would like to add this to a c# WPF TreeView.
What would be the best way to do so?
Is there a step-by-step tutorial for this? Since I'm getting confused with all the MVVM Classes etc.
Thanks
I'm assuming your objects in your list have some sort of list.
if thats the caseyou should look at using the hierarchicaldatatemplate.
a simple example might be something like the following. This is from a segment I used with a radtreeview but it should work the same.
<!-- xaml -->
<UserControl.Resources>
<Style x:Key="_treeViewItemStyle" TargetType="telerik:RadTreeViewItem">
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={StaticResource VisibilityConverter}}" />
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<UI:HierarchicalDataTemplate x:Key="_treeViewTemplate"
ItemsSource="{Binding NodeItems}"
>
<TextBlock Margin="3,0,0,0" Text="{Binding Header}">
</TextBlock>
</UI:HierarchicalDataTemplate>
</UserControl.Resources>
<telerik:RadTreeView x:Name="_pageNavigator" ItemsSource="{Binding InspNavList}" ItemDoubleClick="SendFocusToPage" SelectedItem="{Binding SelectedNavItem, Mode=TwoWay}" ItemTemplate="{StaticResource _treeViewTemplate}" ItemContainerStyle="{StaticResource _treeViewItemStyle}" />
You can use
treeview.ItemsSource = MyList.
Here is a tutorial how to do it using MVVM.
There's no best way to do things. Well, actually there is, but you need a bit more data about the system itself and the current situation (as well as some profound knowledge) to know the best way to do things. Well, putting that aside.
If you'd like to use binding, you could do the following:
a. In your page/window/usercontrol set DataContext property to point to your object (MyResult).
b. In your XAML file use following snippet to bind treeView items to that list:
<TreeView ItemsSource={Binding MyItems}>
....
</TreeView>
c. Enjoy the result.
There are several things you need to consider, though:
1. You should implement DataTemplate for your MyItems objects. Default implementation would just take ToString() result to put into the tree view.
2. If you'd like to use hierarchical data (meaning the one that has levels), you should implement HierarchicalDataTemplate and point where to get children for every node in the tree.
3. You should consider using ObservableCollection for correct binding - this way every addition/deletion of an item in the list would invoke changes in the UI.
Here are several links to get you started: first, second, third.

Trouble binding WPF Menu to ItemsSource

I would like to avoid having to build a menu manually in XAML or code, by binding to a list of ICommand-derived objects. However, I'm experiencing a bit of a problem where the resulting menu has two levels of menu-items (i.e. each MenuItem is contained in a MenuItem):
My guess is that this is happening because WPF is automatically generating a MenuItem for my binding, but the "viewer" I'm using actually already is a MenuItem (it's derived from MenuItem):
<ContextMenu
x:Name="selectionContextMenu"
ItemsSource="{Binding Source={x:Static OrangeNote:Note.MultiCommands}}"
ItemContainerStyleSelector="{StaticResource separatorStyleSelector}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<Viewers:NoteCommandMenuItemViewer
CommandParameter="{Binding Source={x:Static OrangeNote:App.Screen}, Path=SelectedNotes}" />
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
(The ItemContainerStyleSelector is from http://bea.stollnitz.com/blog/?p=23, which allows me to have Separator elements inside my bound source.)
So, the menu is bound to a collection of ICommands, and each item's CommandParameter is set to the same global target (which happens to be a collection, but that's not important).
My question is, is there any way I can bind this such that WPF doesn't automatically wrap each item in a MenuItem?
Unfortunately, the best way I've found to work around this issue is to use a style for the MenuItems, rather than an ItemTemplate. Then each property in the style can be bound to properties on your object. Something like this, for example:
<Style x:Key="SelectionContextMenuStyle" TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Text}" />
<Setter Property="Command" Value="{Binding Path=Command}" />
<Setter Property="CommandParameter" Value="{Binding Path=Parameter}" />
</Style>
It really seems like an ItemTemplate should work, and it would be the better way to go, but this is the only way I've found that actually works properly.
I would be inclined to subclass ContextMenu and override GetContainerForItemOverride:
public class ContextMenuWithNoteCommands : ContextMenu
{
protected virtual DependencyObject GetContainerForItemOverride()
{
return new NoteCommandMenuItemViewer();
}
}
Then set the CommandParameter binding in the NoteCommandMenuItemViewer style, or in ContextMenu.ItemContainerStyle, whichever is more appropriate.
This presumes you can't simply use ItemContainerStyle on a regular MenuItem to get the effect you want:
<ContextMenu ...>
<ContextMenu.ItemContainerStyle>
<Style>
...
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>

Categories