MVVM Binding View specific DataType - wpf

I want to know how you would bind a View specific data type to your View from a ViewModel.
To be more concrete I have a ContextMenu as part of a DatagridView. Now I can bind the ItemSource of my ContextMenu to either a List of MenuItems or a List of Strings (<= implementing a Converter on this).
Both options work fine, but I want to know which is the best and why. I am asking cause I have read that I should try to not use the System.Windows.* Namespace in my ViewModel, and when I Bind a List of MenuItems of course I use this Namespace but on the other Hand if I bind strings and just convert it... This feels weird.
SampleCode (using Caliburn):
<DataGrid x:Name="OverviewItems">
<DataGrid.ContextMenu>
<ContextMenu ItemsSource="{Binding AllColumns, Converter={StaticResource String_Menu_Converter}}" >
<!-- Alternative: <ContextMenu ItemsSource="{Binding AllColumns}" >-->
<ContextMenu.ItemContainerStyle>
<Style>
<Setter Property="cal:Message.Attach" Value="[Event Click]=[SetVisibilityExecute($_clickeditem)]" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>

In WPF, we manipulate data object, not UI objects. So it is true that you should not have any UI objects in your view model. The correct way to get around this issue is to manipulate data objects in the view model that we can then data bind with the UI controls that we declare in DataTemplates. So in your case, you could have a collection of custom objects that have a property to bind to the MenuItem.Header property and another to bind to the MenuItem.Command property for example:
<DataTemplate DataType="{x:Type YourPrefix:YourCustomType}">
<MenuItem Header="{Binding Header}" Command="{Binding Command}" />
</DataTemplate>
...
<Menu ItemsSource="{Binding YourCustomTypeCollection}" />

Related

Caliburn Micro with Treeview Context Menu

I have my hierarchical treeview binding wonderfully to my ViewModel using Caliburn Micro. (The ViewModel has an Items property that returns an ObservableCollection - the treeview is named to this Items property - nothing wrong with the binding).
However the issue comes up with the context menu. The menu fires a method on an instance of the object that the treenode represents. What I rather want to achieve, is to have the menu fire a method on my root ViewModel, passing to it as a parameter the instance of the object represented by the clicked treenode.
Here is my XAML:
<HierarchicalDataTemplate DataType="{x:Type m:TaskGrouping}"
ItemsSource="{Binding Children}">
<Label Content="{Binding Name}"
FontWeight="Bold">
<Label.ContextMenu>
<ContextMenu>
<MenuItem Header="Add New SubFolder"
cal:Message.Attach="AddNewSubfolder" />
<MenuItem Header="Remove this folder"
cal:Message.Attach="RemoveFolder" />
</ContextMenu>
</Label.ContextMenu>
</Label>
</HierarchicalDataTemplate>
What changes do I need to make to my XAML in order to achieve what I want?
ContextMenus are located in a separate visual tree from everything else - it can be a pain to get the bindings right (I often have 10-15 minutes of fighting the bindings on them to get them right!)
You've got your Message.Attach attached property set, all you need to do is ensure that the action target is pointing to the VM rather than the data item. You can use Action.TargetWithoutContext to specify the target for actions (CM will otherwise use DataContext)
You will also need to get a binding path which points to the other visual tree - try using RelativeSource bindings - the ContextMenu also has a property called PlacementTarget which should point to the element that the ContextMenu is attached to
So possibly:
cal:Action.TargetWithoutContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=Label}}"
or
cal:Action.TargetWithoutContext="{Binding PlacementTarget.DataContext}"
You might have to experiment as I often get this almost right first time!
EDIT by OP(Shawn):
This is what worked for me eventually:
<Label Content="{Binding Name}"
Tag="{Binding DataContext, ElementName=LayoutRoot}">
<Label.ContextMenu>
<ContextMenu
cal:Action.TargetWithoutContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Run Task Now" cal:Message.Attach="SomeRootViewModelMethod($dataContext)" />

DataGrid to Observable collection of custom class object issue

I'm somewhat of a novice, so I'm pretty flaky when it comes to binding. I've searched for quite some time, but I simply cannot find any examples that get me to where I need.
I have a DataGrid bound to an observable collection. It is a collection of a custom class that contains several properties (one of which is, itself an observable collection). The user inputs text into the DataGrid and it updates the Observable collection. I would like users to be able to right click on a row in the DataGrid and get a context menu with items generated from the observable collection property of the item in the parent observable collection.
For simplicity, my DataGrid is bound to InputItemList, which is an Observable Collection of InputItem.
InputItemList as ObservableCollection(Of InputItem)
InputItem has properties:
Part_Number as String
Drawing_List as ObservableCollection(Of DrawingItem)
DrawingItem has properties:
Revision as String
Drawing_Path as String
The DataGrid is bound via ItemsSource to InputItemList, and is working properly.
For the life of me, I cannot create a series of menu items that are bound to the Drawing_List collection
Here's a simplified version of my XAML with ??? where I need help with the binding:
<DataGrid x:Name="mw_DataGrid" ItemsSource="{Binding Source={StaticResource InputItemList}}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Part_Number}" CanUserReorder="False" CanUserResize="False" CanUserSort="False" Header=""/>
<DataGrid.Columns>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Open Obsolete Revision" ItemsSource=????>
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding Path=Revision}"/>
<Setter Property="MenuItem.Command" Value="{StaticResource cmdOpenObsPDF}" />
<Setter Property="MenuItem.CommandParameter" Value="{Binding Path=Drawing_Path}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
Create a property on your view model, of type InputItem, that represents the currently selected row. Name it for example CurrentlySelectedInputItem
Then bind that to the DataGrids SelectedItem.
Then bind the itemsource you are wanting to:
<MenuItem Header="Open Obsolete Revision" ItemsSource="{Binding CurrentlySelectedItem.DrawingList}">
Not 100% sure of the menu Item usage - but that is how to bind to the drawing_List you are after, you should be able to modify it to suit your needs.
Ok, I figured it out.
I actually had to do this once already and totally forgot!
Apparently there's some difficulty searching back through the tree with context menus.
I do not remember the full explanation, but here's the XAML that works for me:
<MenuItem Header="Open Obsolete Revision" ItemsSource="{Binding Path=PlacementTarget.SelectedItem.DrawingList, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}">

Using TemplateBinding inside DataTemplates

I'm creating a custom control, and I'm having problems binding UI elements inside a DataTemplate to the custom control's dependency properties.
There's a content control in my control that should change its content and content template according to a certain property, so I bound it this way -
<ContentControl Content="{TemplateBinding ControlMode}" ContentTemplateSelector="{StaticResource TemplateSelector}"/>
The Content Template selector is defined this way -
<ns:TemplateSelector x:Key="TemplateSelector">
<ns:TemplateSelector.Template1>
<DataTemplate>
<TreeView ItemsSource="{TemplateBinding TreeSource}"/>
</DataTemplate>
</ns:TemplateSelector.Template1>
<ns:TemplateSelector.Template2>
<DataTemplate>
<ListView ItemsSource="{TemplateBinding ListSource}"/>
</DataTemplate>
</ns:TemplateSelector.Template2>
</ns:TemplateSelector>
The problem is that the TreeView and the ListView can't be bound to their itemssource with TemplateBinding due to this error for example -
"Cannot find TreeSourceProperty on the type ContentPresenter"
I've been looking around for an answer and I found this answer that simple states that this is impossible.
How to use template binding inside data template in custom control (Silverlight)
So if this really is impossible, how else could I bind the elements inside my template to the DependencyProperties of the CustomControl?
Thanks!
In WPF you can use a binding with RelativeSource targeting the "templated" control.
e.g.
{Binding TreeSource,
RelativeSource={RelativeSource AncestorType=MyCustomControl}}
Edit: If you have a break in a tree you could possibly work around that by passing that control around, e.g.
<ControlThatOwnsPopup
Tag="{Binding RelativeSource={RelativeSource AncestorType=MyCustomControl}}">
<Popup>...
<TreeView ItemsSource="{Binding PlacementTarget.Tag.TreeSource,
RelativeSource={RelativeSource AncestorType=Popup}}"/>

Binding to two different DataContexts in a ContextMenu

I'm trying to bind to a property of a container from inside a DataTemplate. A simplified version of my markup looks like:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type myCustomItem}">
<!--Visual stuff-->
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Item"
Command="{Binding myCustomItemsICommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type CustomContainerType}}, Path=ContainerProperty}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<CustomContainerType/>
</Grid>
My approach is based on this post but it doesn't seem to be working. The issue seems to arise from the placement of the ContextMenu within the visual tree. Basically I am trying to bind the Command to the DataContext of the DataTemplate but bind the CommandParameter to a DataContext outside the DataTemplate.
ContextMenus are not in the same visual tree as the rest of the controls, there are a few questions regarding how to do bindings accross that boundary but this might be somewhat difficult without specifying names.
ElementName fails as well because of the lacking tree connection, but you could use x:Reference in the Binding.Source instead.

XAML Control array for static number of combo boxes

I would like an array of 10 combo boxes on a wpf form.
The ItemsSource of the combo boxes are identical - an ObservableCollection of selectable Items.
Each Selected Item will bound to an item in a different ObservableCollection, imaginatively called 'SelectedItems'..
What is the best way to do the array? I could of course have 10 separate combo boxes but this would be not very elegant..
I don't think an ItemsControl template is what I'm after as the number of combo boxes is static.
Thanks
Joe
If I understand you right, you have 10 ComboBoxes with the same item list, but different data sources
In that case, I could create a common style for the ComboBox which sets the common properties such as ItemsSource (and SelectedItem if the binding is the same for all items), and then actually create the individual ComboBoxes on the form as needed.
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type ComboBox}">
<!-- Set the binding to wherever your ItemsSource resides. In this
case,I'm binding to a static class called Lists and a static
property called ComboBoxItems -->
<Setter Property="ItemsSource"
Value="{Binding Source={x:Static local:Lists.ComboBoxItems}}" />
<!-- Only use this setter if your binding is the same everywhere -->
<Setter Property="SelectedItem" Value="{Binding SelectedItem}" />
</Style>
</StackPanel.Resources>
<ComboBox DataContext="{Binding Item1}" />
<ComboBox DataContext="{Binding Item2}" />
<ComboBox DataContext="{Binding Item3}" />
<ComboBox DataContext="{Binding Item4}" />
<ComboBox DataContext="{Binding Item5}" />
<ComboBox DataContext="{Binding Item6}" />
<ComboBox DataContext="{Binding Item7}" />
<ComboBox DataContext="{Binding Item8}" />
<ComboBox DataContext="{Binding Item9}" />
<ComboBox DataContext="{Binding Item10}" />
</StackPanel>
Of course, if the DataSource for your ComboBoxes CAN be put in a collection, it is preferred that they are and that you use an ItemsControl to display the ComboBoxes
<ItemsControl ItemsSource="{Binding SelectedItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding }"
ItemsSource="{Binding Source={x:Static local:Lists.ComboBoxItems}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Each Selected Item will bound to an item in a different ObservableCollection, imaginatively called 'SelectedItems'..
Given that you're effectively binding to a collection, I would do an ItemsControl template, and just treat it that way. Unless you want to customize the layout (ie: these won't be arranged together in the View), that will simplify the design, even if the number of items is always "static".
If you want to have the items arranged separately on the View, then just having 10 combo boxes may be more appropriate.
Personally I think an ItemsControl which has an ItemTemplate which constructs each ComboBox is the way to go! Are you always going to have exactly 10 of these?
From an MVVM perspective, I can imagine a parent View Model which has a collection of selection view models. Each selection view model will have the list of items that can be selected, and the currently selected item. This view model will easily bind to your view.
Not knowing why you need this... which would probably be important in deciding how to do this... here is my stab at it.
Why not design the UI, give each ComboBox a name, create a List and Add each into that List at runtime?

Resources