Currently I'm in a team building a datagrid that contains data coming from a viewmodel. We are using reactive-ui and each "row" in the datagrid is represented by a viewmodel class that inherits from "ReactiveObject".
There are 2 properties in the viewmodel tha tI'm interested in to fill a combobox in each row. A collection of PossibleScales and the DefaultScale. The PossibleScales are loaded into the combobox and the DefaultScale is used to set the selecteditem to the DefaultScale. The DefaultScale is always contained by the DefaultScale collection. What I want to achieve is that any non-default scale in the combobox is formatted in italics. So I created a style that invokes a IMultiValueConverter. so, I need to give this IMultiValueConverter 2 parameters, the current scale (from the collection of PossibleScales and the DefaultScale.
Here's the XAML that we have to fill the combobox with all the items in the list of PossibleScales (I have the same for EditingElementStyle):
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=PossibleScales}" />
<Setter Property="ItemContainerStyle" Value="{StaticResource Measeurementscale}"></Setter>
</Style>
</DataGridComboBoxColumn.ElementStyle>
The Measeurementscale StaticResource is defined as a style in the resources section of the control.
<Style x:Key="Measeurementscale" TargetType="ComboBoxItem">
<Setter Property="FontStyle">
<Setter.Value>
<MultiBinding Converter="{StaticResource NonDefaultScaleToItalicConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="DataContext"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
As you can see, the current value of the comboboxitem is passed to the IMultiValueConverter, so that's great. But now I'm wondering how I can pass the DefaultScale (which is a property of the viewmodel and defined as DataContext for the control) to this converter as well. I have not been able to do this.
Any help would be appreciated.
You can use FindAncestor to get the parent control where this data context is set, and bind to it. For example:
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ComboBox}"
Path="DataContext.DefaultScale" />
Related
I am trying to work with the MVVM principles within a small WPF project using C#.
I have a ListBox that is populated with CheckBoxes created through binding back to the ViewModel. I also have a command bound to the CheckBoxes and wish to pass the CheckBoxes Content as a CommandParameter. I was looking for something like this:
<Binding ElementName="" Path="Content"/>
Unfortunately, because the CheckBoxes are created through a binding I don’t have the element name.
The code for ListBox / ListBoxItem Style is this:
<Style x:Key="CheckBoxListStyle" TargetType="{x:Type ListBox}">
<Setter Property="SelectionMode" Value="Multiple"></Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<CheckBox Command="{Binding SelectedItemCommand, Mode=OneWay, Source={StaticResource comd}}">
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource cv}">
<Binding ElementName="" Path="Content"/>
<Binding ElementName="" Path="IsChecked"/>
</MultiBinding>
</CheckBox.CommandParameter>
<ContentPresenter></ContentPresenter>
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
And its implementation is:
<ListBox Grid.Row="1" Style="{StaticResource CheckBoxListStyle}" Name="lstProducts" ItemsSource="{Binding stampInfo, Mode=OneWay, Source={StaticResource vmStamp}}"
DisplayMemberPath="Country" >
</ListBox>
Ultimately my goal is to be able to display the text Contents (Countries in this case) of all the selected items in a text box were each country is separated by a comma. The only thing I am currently missing is the Country.
Do not create a ControlTemplate for ListBoxItem when you really want to display your data items differently, use a DataTemplate instead, that is exactly its purpose. See Data Templating Overview.
Remove the DisplayMemberPath from the ListBox, as you cannot use both use a path and a custom DataTemplate at the same time. You would only set this path, if there was no DataTemplate, but you wanted to specify a concrete property or property path to display.
<ListBox Grid.Row="1"
Style="{StaticResource CheckBoxListStyle}" Name="lstProducts"
ItemsSource="{Binding stampInfo, Mode=OneWay, Source={StaticResource vmStamp}}"/>
Replace the ControlTemplate with a DataTemplate as ItemTemplate. Then bind the Content and CommandParameter to the property Country. The data context is automatically set to the corresponding item in the bound collection of data items. The IsChecked property can be bound using a RelativeSource, which is the CheckBox itself.
<Style x:Key="CheckBoxListStyle" TargetType="{x:Type ListBox}">
<Setter Property="SelectionMode" Value="Multiple"></Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2" />
</Style>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox Content="{Binding Country}"
Command="{Binding SelectedItemCommand, Mode=OneWay, Source={StaticResource comd}}">
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource cv}">
<Binding Path="Country"/>
<Binding Path="IsChecked" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</CheckBox.CommandParameter>
</CheckBox>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
A different option would be to create data items with a property that can be bound to the IsChecked property of CheckBox. Then you could act in the setter of the data item or on e.g. a button click that executes a command, which filters the bound collection in your view model for checked items.
I created an Index property on the object in the list the ItemsControl's ItemsSource is bound to, but when I put a breakpoint in my converter I see the value for that binding is DependancyProperty.UnsetValue. The data context for the ContentPresenter is that object, there is a property on the object, why doesn't it see the Index property?
<ItemsControl ItemsSource="{Binding Charts}" x:Name="ItemsControl">
<ItemsControl.ItemTemplate>
<ItemContainerTemplate >
<ContentPresenter Content="{Binding}">
<ContentPresenter.Visibility>
<MultiBinding Converter="{StaticResource Converter}">
<Binding Path="Index"/>
<Binding Path="WhichAreVisible" />
</MultiBinding>
</ContentPresenter.Visibility>
</ContentPresenter>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
ItemTemplate is the wrong place to do that. That's going to be a DataTemplate or ItemContainerTemplate used to display the content of the items in the ItemsControl, within some other child container control -- one per child. That other container control is what you want to hide, not just the content within it. Of course if it's auto-sized with no padding or margin, it won't take up any space once the content collapses, but then you're counting on that remaining the case.
Try this and see what you get; ItemContainerStyle controls the style of the actual child item. In a ListBox, it would be applied to the ListBoxItem type; in an ItemsControl, the children are ContentPresenters. If you already have an ItemContainerStyle, just add this trigger to it.
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource Converter}">
<Binding Path="Index"/>
<Binding Path="WhichAreVisible" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
Instead of using many DataTriggers with MulitBinding to set the background color of a ListViewItem:
<DataTrigger Value="4" >
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource WaitStatus}">
<Binding Path="Checkin" />
<Binding Path="Checkout" />
<Binding Path="Notseen" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="{StaticResource GreenBrush}" />
</DataTrigger>
I am attempting to use an attached property to return the Brush color for the ListViewItem background. The code looks something like:
<Style x:Key="listViewItemStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="v:ListViewItemBehavior.MyValue" Value="{Binding testvalue}"/>
<Setter Property="Background" Value="{Binding Path=(v:ListViewItemBehavior).Background}" />
My goal is for the first setter to deliver the DataContext of the ListViewItem to the attached property at v:ListViewItemBehavior.MyValue. The attached property then calculates the background color and places the result in ListViewItemBehavior.Background.
The second setter is then to pull the background color from the ListViewItemBehavour and set the ListViewItem's background color from it.
The attached property, ListViewItemBehaviour works correctly to set the Background color, but I can't get how to return this color back to the XAML for the second setter. The {Binding...} of the value in the second setter always looks for the v:ListViewItemBehavior in the DataContext of the ListViewItem--and I can not get it to look in the ListViewItemBehaviour for the Background color.
How does one use a different datacontext for the Setter Value property then the inherited datacontext from the ListViewItem? (syntax please).
TIA
Set the RelativeSource of the Binding to Self, i.e. the styled ListViewItem:
<Setter Property="Background"
Value="{Binding Path=(v:ListViewItemBehavior).Background,
RelativeSource={RelativeSource Self}}" />
I'm working on a custom class that descends from DataGridColumn. The base class for DataGridColumn is DependencyObject. As such, it does not have a Tooltip property.
I want my custom class to have a Tooltip property. Actually, I want it to also have a ToolTipTemplate property that is a DataTemplate that can be used to generate the ToolTip. How do I go about adding this functionality to my class?
Tony
Its a common misconception that DataGridColumn being a dependency object, is part of the visual tree. It is not. So even if we create an inheritable dependency property (just like DataContext or FlowDirection which automatically propagates down the visual parent to its child elements), the new property of ToolTip wont descend down to individual cells, as those cells are not the children of the data grid column.
So now that we know this, the only way left is to add a binding in the CellStyle and bind to the self Column.ToolTip property. Just because you have decided to go with ToolTipTemplate, then you could add a ContentControl and then bind to its content template.
Something like this...
<tk:DataGrid x:Name="MyDataGrid" RowHeaderWidth="15"
ItemsSource="{StaticResource MyData}"
AutoGenerateColumns="False">
<tk:DataGrid.CellStyle>
<Style TargetType="{x:Type tk:DataGridCell}">
<Setter Property="ToolTip">
<Setter.Value>
<ContentControl
ContentTemplate="{Binding Column.ToolTipTemplate,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type tk:DataGridCell}}}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger
Binding="{Binding Column.ToolTipTemplate,
RelativeSource={RelativeSource Self}}"
Value="{x:Null}">
<Setter Property="ToolTip" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</tk:DataGrid.CellStyle>
....
</tk:DataGrid>
I am attempting to use a <MultiBinding> to pass two parameters to my ViewModel's command but am having issues getting the XAML parser to accept my attempts.
Consider the following ListView contained within my UserControl which is bound to a collection of Ticket objects.
<ListView x:Name="lvTicketSummaries"
ItemsSource="{Binding TicketSummaries}"
ItemContainerStyle="{DynamicResource ResourceKey=ListViewItem}"
IsSynchronizedWithCurrentItem="True">
The respective style ListViewItem
<Style x:Key="ListViewItem" TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{DynamicResource ResourceKey=cmListViewItem}"/>
<!-- A load of irrelevant stuff ->
</Style>
And the referenced ContextMenu item where the source of my query sits;
<!-- ContextMenu DataContext bound to UserControls view model so it can access 'Agents' ObservableCollection -->
<ContextMenu x:Key="cmListViewItem" DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext}">
<MenuItem Header="Send as Notification" ItemsSource="{Binding Path=Agents}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<!-- Display the name of the agent (this works) -->
<Setter Property="Header" Value="{Binding Path=Name}"/>
<!-- Set the command to that of one on usercontrols viewmodel (this works) -->
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.SendTicketNotification}" />
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding Converter="{StaticResource ResourceKey=TicketNotificationParameterConverter}">
<MultiBinding.Bindings>
<!-- This SHOULD be the Agent object I clicked on to trigger the Command. This does NOT work, results in either exception or 'UnsetValue' -->
<Binding Source="{Binding}" />
<!-- Also pass in the Ticket item that was clicked on to open the context menu, this works fine -->
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ListView}}" Path="SelectedItem" />
</MultiBinding.Bindings>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
So here is what I am attempting to do;
The context menu has a single item "Send ticket as notification" which, when selected, lists all the available Agents which can receive said notification. This works.
When I click on one of these agent options, I want the context menu item to send both the ticket item that was clicked on from the listview to show the context menu (this works) AND the Agent item I selected from the context menu. I have half-achieved this with the MultiBinding
<MultiBinding Converter="{StaticResource ResourceKey=TicketNotificationParameterConverter}">
<MultiBinding.Bindings>
<!-- This works, it sends the Ticket object to the Converter -->
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ListView}}" Path="SelectedItem" />
<!-- This caused an exception saying that I can only set 'Path' property on DependencyProperty types -->
<Binding Path="{Binding}" />
</MultiBinding.Bindings>
</MultiBinding>
Now although the actual set-up of the context menu seems some what complicated to me. The actual specification that one of the MultiBinding parameters should be the actual item bound to the clicked ContextMenu.MenuItem seems quite a simple command. The context menu correctly lists all Agents and so I would of thought that simply asking for this current object to be sent through as a command parameter, simple. I have also tried;
<Binding RelativeSource={RelativeSource AncestorType={x:Type MenuItem}} Path="SelectedItem" />
as well as
<Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}} Path="SelectedItem" />
But all that gets sent to the Parameter converter is UnsetValue
Thank you for your time and any suggestions you might have.
This line is odd:
<Binding Path="{Binding}" />
Is the current DataContext a string that would suffice as a path that would find something in the current DataContext which is in fact not a string after all? Makes perfect sense.
Did you mean to do this instead which binds to the DataContext?
<Binding />