WPF object binding from parent itemscontrol - c#

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>

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."

Updating the source of a Listbox with a multibinding

I'm trying to get a multi-bound listbox to call the convert back method when items inside it change. Specifically I have a datatemplate that maps each entry to a checkbox and I'd like the source to be updated whenever a box is checked.
<ListBox x:Name="listBoxEdit" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="3" Height="100" >
<ListBox.ItemsSource>
<MultiBinding Converter="{StaticResource selectedLoadsConverter}" >
<Binding Path="AvailableLoads"/>
<Binding Path="CurrentUnit"/>
</MultiBinding>
</ListBox.ItemsSource>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Item1}" IsChecked="{Binding Item2}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I know the problem lies in the fact that I'm binding to to the ItemsSource property which is never actually being updated by the checkbox changing, but I can't figure out a good way to push the updates up to the source.

WPF Multibinding .Net Framework 4.0

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>`

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