WPF Multibinding .Net Framework 4.0 - c#

I have the following DataGridTemplate column:
<DataGridTemplateColumn x:Name="specialtiesColumn" Header="Specialties" MinWidth="170">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Specialties, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Height="17" VerticalAlignment="Center" Orientation="Horizontal">
<CheckBox Width="20">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource ProviderSpecialtyIsInSpecialtiesConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=ComboBox}" Path="DataContext.Specialties" />
<Binding Path="Name" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
<TextBlock Text="{Binding Name}" Width="130" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
What I am trying to do is have a column of comboboxes inside a datagrid, and each combobox have several checkboxes. Each row of the datagrid represent hospitals. The combobox will show which specialties the hospital has, and the user should also be able to modify these selections.
This is the code for the converter:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
try
{
HashSet<Specialty> specialties = (HashSet<Specialty>)values[0];
string specialty = (string)values[1];
foreach (Specialty s in specialties)
{
if (s.Name == specialty)
return true;
}
return false;
}
catch (Exception)
{
return false;
}
}
This works on computers with .Net Framework 4.5, but it crashes when trying to load with only .Net Framework 4.0. The project is targeted for .Net Framework 4.0.

I suppose the reason for that is that the MultiBinding is using the RelativeSource and the DataGridColumn is not a part of visual tree. They must have fixed the column binding behaviour in 4.5. I got the same issue woth my code that looked like this:
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource directionConverter}">
<MultiBinding.Bindings>
<Binding ElementName="clientPerspective" Path="IsChecked"/>
<Binding Path="Direction"/>
</MultiBinding.Bindings>
</MultiBinding>
</DataGridTextColumn.Binding>`

Related

WPF Binding in textblock does not work but in its tooltip does

I have a weird issue.
In my application (witten with C# .net framework 4.8 with Microsoft's MVVM toolkit) I have a ListView bound to a BindingList<VisualMachine>, everything works as expected.
Now I wanted to add to the Itemtemplate a DockPanel with data from another BindingList<PreviewParameter> (another property of the original VisualMachine object ).
This is when weirdness starts: In this new ItemTemplate I have a Grid that contains an Image, a TextBlock and a Tooltip. The TextBlock and the Tooltip are bound to the same object, but only the ToolTip actually show data!
I feel like it's some DataContext Issue, but I am not able to pin it... Can anybody give me some advice?
Thanks!
The code:
<ListView Name="MachinesListView" Margin="2" HorizontalContentAlignment="Stretch" BorderThickness="0" DockPanel.Dock="Top" ItemsSource="{Binding Path=Machines}" PreviewMouseRightButtonDown="ListView_PreviewMouseRightButtonDown" SelectedItem="{Binding Path=SelectedMachine}">
<ListView.ItemTemplate>
<DataTemplate>
<!-- Other stuff -->
<DockPanel>
<!--#region PREVIEW PARAMETERS PANEL-->
<WrapPanel HorizontalAlignment="Stretch" DockPanel.Dock="Bottom" Visibility="{Binding Path=PreviewParameters.Count, Converter={StaticResource CountToVisConverter}}">
<ListView ItemsSource="{Binding Path=PreviewParameters}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid MinWidth="50">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="Auto" MinWidth="25"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding Path=Icon, Converter={StaticResource ResourceKey=BmpToBmpImageConverter}}"/>
<TextBlock Grid.Column="1" MaxWidth="100" DataContext="{Binding}">
<TextBlock.Text>
<!-- THIS DOES NOT APPEAR -->
<MultiBinding StringFormat="{}{0} [{1}]}">
<Binding Path="Parameter.VisualizedValue"/>
<Binding Converter="{StaticResource ParamToEnumDescConverter}" Path="Parameter"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Grid.ToolTip>
<TextBlock MaxWidth="100" TextWrapping="Wrap">
<TextBlock.Text>
<!-- THIS DOES APPEAR -->
<MultiBinding StringFormat="{}{0} [{1}] - {2} TEST:{3} {4}">
<Binding Path="Parameter.Reference"/>
<Binding Path="Parameter.SetupIndex"/>
<Binding Path="Description"/>
<Binding Path="Parameter.VisualizedValue"/>
<Binding Converter="{StaticResource ParamToEnumDescConverter}" Path="Parameter"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid.ToolTip>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</WrapPanel>
<!-- Other stuff -->
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What happens:
I found it.
There was a } in the StringFormat property of the TextBlock's MultiBinding.
So instead of
<MultiBinding StringFormat="{}{0} [{1}]}">
it should be
<MultiBinding StringFormat="{}{0} [{1}]">
And BAM! Now it works.
I'll leave the question as the following reminder to anybody: "Write your code, review it, than go take a coffee o chat a little and then come back. You'll be amazed on how blind you could be."

WPF object binding from parent itemscontrol

I have Groups collection property in my viewmodel and each group has its Teams collection.
I want to remove a team from group with a button click. How can I bind the group in which the team is to the command parameter? This solution is not working:
<ItemsControl ItemsSource="{Binding Groups}" Name="gSource" Tag="{Binding .}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Teams}" Name="tSource">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Path=DataContext.RemoveTeamFromGroupCommand, ElementName=gSource}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource ObjectsConverter}">
<Binding Path="."/>
<Binding Path="Tag.Value" RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It binds the team properly, but instead of group it binds MS.Internal.NamedObject with unset value.
#Tam Bui is perfectly right about using the DataContext of the parent ItemsControl.
But besides "RelativeSource AncestorType", you can name the UI elements inside the DataTemplate (you assigned the tSource) and use them.
You used a similar binding to bind the command.
Also, Binding by default provides a link to the current data context.
It is not necessary to give it Path.
Alternative solution:
<Button Command="{Binding Path=DataContext.RemoveTeamFromGroupCommand, ElementName=gSource}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource ObjectsConverter}">
<Binding/>
<Binding Path="DataContext" ElementName="tSource"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
The Tag is unnecessary if you want the Group to be the 2nd binding of your MultiBinding. Instead, just bind it to the DataContext of the relative ancestor ItemsControl. I tested this and it worked.
<MultiBinding Converter="{StaticResource ObjectsConverter}">
<Binding Path="."/>
<Binding Path="DataContext" RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
</MultiBinding>

Checkbox's IsChecked property never changes

I have a TreeView setup with a HierarchialDataTemplate. It's ItemsSource is bound to a collection of Overlay objects in my viewmodel, where each Overlay has a collection of Layer objects (thus the HierarchialDataTemplate). For each Overlay, I'm displaying a CheckBox and a Label which is simply bound to the Overlay's Name property.
Each time one of the checkboxes is checked/unchecked, the current Overlay and the IsChecked property of the CheckBox will be sent as command parameters to my viewmodel. I'm using a MultiValueConverter to send these.
My issue is that the IsChecked property of the CheckBox never changes. I've tried using both DataTriggers and setting the Command property directly, but I get the same result.
Below is the related .xaml for the TreeView. This is using the Command property.
<TreeView ItemsSource="{Binding OverlaysViewSource}" Name="LayersTreeView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Layers}" >
<StackPanel>
<CheckBox IsChecked="True" Command="{Binding DataContext.SetLayersCmd, RelativeSource={RelativeSource AncestorType=UserControl}}">
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource multiValueConverter}">
<Binding RelativeSource="{RelativeSource Self}" />
<Binding />
</MultiBinding>
</CheckBox.CommandParameter>
</CheckBox>
<Label Content="{Binding Name}" />
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Here's the version using DataTriggers:
<TreeView ItemsSource="{Binding OverlaysViewSource}" Name="LayersTreeView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Layers}" >
<StackPanel>
<CheckBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding DataContext.SetLayersCmd, RelativeSource={RelativeSource AncestorType=UserControl}}" >
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource multiValueConverter}">
<Binding RelativeSource="{RelativeSource AncestorType=CheckBox}" />
<Binding/>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding DataContext.SetLayersCmd, RelativeSource={RelativeSource AncestorType=UserControl}}" >
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource multiValueConverter}">
<Binding RelativeSource="{RelativeSource AncestorType=CheckBox}"/>
<Binding />
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
<Label Content="{Binding Name}" />
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Here's my MultiValueConverter:
public class MultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
CheckBox cb = (CheckBox)values[0];
Overlay overlay = (Overlay)values[1];
return new object[] { cb.IsChecked, overlay };
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I'm checking the IsChecked value in both the converter and the command in my view model. It never changes. The GUI never reflects a change in the CheckBox either.

How to get a parent value in multibinding

I'm using dataTemplate. This is the template:
<ItemsControl ItemsSource="{Binding RAM.Partitions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Position, StringFormat={}{0}k}"/>
<Grid Grid.Column="1">
<Border>
<Border.Height>
<MultiBinding Converter="{StaticResource MultiplyConverter}">
<Binding ElementName="LayoutRoot" Path="ActualHeight"/>
<Binding Path="Size" />
<Binding Path="RAM.Size" />
</MultiBinding>
</Border.Height>
</Border>
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Can you see this line?
<Binding Path="RAM.Size" />
That line throws me an exception, it should be because RAM.Size is from a parent element. How might I get that value?
Thanks in advance!
So you're trying to get to the RAM.Size value on the same object that your ItemsControl is getting its ItemsSource from?
See if this works:
<MultiBinding Converter="{StaticResource MultiplyConverter}">
<Binding ElementName="LayoutRoot" Path="ActualHeight"/>
<Binding Path="Size" />
<Binding Path="DataContext.RAM.Size"
RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=ItemsControl}" />
</MultiBinding>
So the binding is going up in through the visual tree to the ItemsControl, then binding to the Ram.Size property of its DataContext.

Compound DisplayMemberPath for a combobox

I need to create a DisplayMemberPath that is a compound of a few properties (ie object.category.Name+" -> "+object.description) I'm pretty sure I can do this by creating a dynamic data type that encapsulates the object and also adds a new property called displayField that is what I need but I'm wondering if there is a more proper way to do this that does not involve creating a new object. Any ideas?
DisplayMemberPath is just a "shortcut" for when you don't need a complex template for items. If you need more control, use ItemTemplate instead:
<ComboBox ItemsSource="{Binding Items}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} -> {1}">
<Binding Path="Category.Name" />
<Binding Path="Description" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Categories