How do I bind a command to a Listbox Item? - c#

I have a Listbox. I have the ItemSource of the Listbox bound to a list.
Within the ItemTemplate, I want to be able to add a MouseBinding to each item.
Here's what I have so far:
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="Blue" CornerRadius="8" >
<Border.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="{Binding Test}" CommandParameter="{Binding PropertyOfClickItem}" />
</Border.InputBindings>
<TextBlock Foreground="White" FontSize="24" FontWeight="Bold" Margin="5">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1}
Seat: {2}">
<Binding Path="LastName" />
<Binding Path="FirstName" />
<Binding Path="Seat" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
I'm confused, because it seems that I can only bind to things within my ItemSource, but my "Test" command is not within that. How can I bind to a Command that's in the ViewModel for the View, instead of the ItemSource that's bound to the Listbox?
Not only that, but I want to be able to pass a property of the selected item to the command. Is this possible?

You can use the RelativeSource property of the binding to get an ancestor that has the DataContext you want. For example:
Command="{Binding DataContext.Test, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"

Use a Relative Source like clcto said.
Look here for a complete answer:
WPF "static" binding in a List

Related

Interaction triggers inside DataTemplate not working with XamlReader

I'm trying to parse with XamlReader.Load() a DataTemplate (for a WPF datagrid) created dynamically in code behind :
DataTemplate dataTemplate;
StringReader template = new StringReader($#"
<DataTemplate
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:local=""clr-namespace:MyApp;assembly=MyApp"">
<DataTemplate.Resources>
<local:ArrayMultiValueConverter x:Key=""arrayMultiValueConverter""/>
</DataTemplate.Resources>
<StackPanel Orientation=""Vertical"">
<Expander VerticalAlignment=""Center"" xmlns:i=""http://schemas.microsoft.com/xaml/behaviors"">
<i:Interaction.Triggers>
<i:EventTrigger EventName=""IsExpanded"">
<i:InvokeCommandAction Command=""{{Binding DataContext.TextFilterColumn}}""/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Expander.Header>
<TextBlock x:Name=""{dtColumnName}"" VerticalAlignment=""Center"" Text=""{{TemplateBinding Content}}"" Margin=""5,5,5,0"" FontWeight=""SemiBold""/>
</Expander.Header>
<StackPanel Orientation=""Horizontal"">
<TextBox x:Name=""{"TbxFilter" + dtColumnName}"" Width=""100"" Margin=""5""/>
<TextBlock Margin=""0,0,5,0"" VerticalAlignment=""Center"" FontFamily=""Segoe MDL2 Assets"">
<Hyperlink TextDecorations=""{{x:Null}}"" Command=""{{Binding DataContext.FilterColumnName, RelativeSource={{RelativeSource FindAncestor, AncestorType={{x:Type Window}}}}}}"">
<Hyperlink.CommandParameter>
<MultiBinding Converter=""{{StaticResource arrayMultiValueConverter}}"">
<Binding Path=""Text"" ElementName=""{dtColumnName}"" />
<Binding Path=""Text"" ElementName=""{"TbxFilter" + dtColumnName}"" />
</MultiBinding>
</Hyperlink.CommandParameter>

</Hyperlink>
</TextBlock>
<TextBlock Margin=""0,0,5,0"" VerticalAlignment=""Center"" FontFamily=""Segoe MDL2 Assets"">
<Hyperlink TextDecorations=""{{x:Null}}"" Command=""{{Binding DataContext.FilterColumnName, RelativeSource={{RelativeSource FindAncestor, AncestorType={{x:Type Window}}}}}}"">
<Hyperlink.CommandParameter>
<MultiBinding Converter=""{{StaticResource arrayMultiValueConverter}}"">
<Binding Path=""Text"" ElementName=""{dtColumnName}""/>
<Binding Path=""Text"" RelativeSource=""{{RelativeSource FindAncestor, AncestorType={{x:Type TextBlock}}}}""/>
</MultiBinding>
</Hyperlink.CommandParameter>

</Hyperlink>
</TextBlock>
</StackPanel>
</Expander>
</StackPanel>
</DataTemplate>
");
XmlReader xmlReader = XmlReader.Create(template);
dataTemplate = XamlReader.Load(xmlReader) as DataTemplate;
textColumn.HeaderTemplate = dataTemplate;
All is working when I remove this part of code :
<i:Interaction.Triggers>
<i:EventTrigger EventName=""IsExpanded"">
<i:InvokeCommandAction Command=""{{Binding DataContext.TextFilterColumn}}""/>
</i:EventTrigger>
</i:Interaction.Triggers>
But when I add it, there is an Exception Thrown :
System.Windows.Markup.XamlParseException: ''Cannot set unknown member
'{http://schemas.microsoft.com/xaml/behaviors}Interaction.Triggers'.'
Line number '11' and line position '10'.'
I use "XAML Behaviors" following this article (but the same happened with Interactivity) :
https://devblogs.microsoft.com/dotnet/open-sourcing-xaml-behaviors-for-wpf/
It seems to be an issue with XamlReader.Load(xmlReader).
If someone knows a workaround, I will be grateful.
Configuration :
Framework 4.8 (tried with 4.7.2)
Microsoft.Xaml.Behaviors.Wpf
1.1.39
Thanks.
It's not mentioned in the doucmentation of XamlReader.Load but any custom assemblies referenced in a XAML namespace mapping must already be available to the application.
You have two options:
1.Load assembly Microsoft.Xaml.Behaviors or initialize some type from the assembly before reading xaml input.
Assembly assembly = Assembly.LoadFrom("Microsoft.Xaml.Behaviors.dll");
or
var et = new Microsoft.Xaml.Behaviors.EventTrigger();
2.Use the CLR namespace declaration in xaml
xmlns:i=""clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors""

How to bind button to a delegated command?

I have a list that looks like as follows. In the list I want buttons that do something through a delegated command.
<dxe:ListBoxEdit
Grid.Row="0"
Grid.Column="5"
Margin="0,50,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
DisplayMember="Name"
ItemSource="{Binding Items}"
ItemTemplate="{StaticResource ItemTemplate}"
Name="lista"
SelectionMode="Single"
ScrollViewer.CanContentScroll="True">
</dxe:ListBoxEdit>
and dataTemplate look like this:
<Window.Resources>
<DataTemplate x:Key="ProductsTemplate" >
<dxe:ListBoxEditItem>
<DockPanel>
<Image Source="{Binding Picture}" HorizontalAlignment="Left" Margin="25 50"/>
<TextBlock FontSize="14" TextWrapping="Wrap">
<TextBlock.Text >
<MultiBinding StringFormat="{}{0} Price: {1} TaxRateLevel: {2} ">
<Binding Path="Name" />
<Binding Path="Price" />
<Binding Path="TaxRateLevel" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Button Content="Add" Command ="{Binding AddCommand}"></Button>
</DockPanel>
</dxe:ListBoxEditItem>
</DataTemplate>
</Window.Resources>
If you are using MVVM (Model-View-ViewModel) you can achieve the result with RelayCommand.
In your ViewModel file, you need to declare your command:
public RelayCommand YourCommand { get; set; }
Then, you need to initialize it (in the view model constructor for example):
YourCommand = new RelayCommand(YourMethodName);
Finally, for the XAML, I have this example of a ListBox that you can adapt for your specific situation:
<ListBox
x:Name="Files"
ItemsSource="{Binding Items, Mode=TwoWay}"
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding
Gesture="LeftDoubleClick"
Command="{Binding YourCommand }"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As you can see in this example, we have a ListBox that is binding some data. To set the command, we access TextBlock inside ListBox.ItemTemplate and add the command for the gesture LeftDoubleClick.
If the ViewModel is set to the Window's DataContext, and the AddCommand is set at the main level of the ViewModel, then the command must know what elements it works with.
That is, the command necessarily needs parameter processing.
The data required for the command is retrieved from the parameter.
How exactly this is done depends on the framework you use.
For binding, you can do this:
Give a name to the Window <Window x:Name="main"....
Bind to the DataContext of the Window using binding of the ElementName type and passing the current element in the parameter:
XAML:
<Button Content="Add"
Command ="{Binding DataContext.AddCommand, ElementName=main}"
CommandParameter="{Binding}"/>
Also, the layout of the elements is not clear from your question.
In the ListBox, you are using a template named "ItemTemplate" for the item, but you didn't show it.
You are showing the "ProductsTemplate" template, but where it is used is not clear.

ComboBox is displaying System.Data.DataRowView no matter what is selected. ItemTemplate Issue

I have a ComboBox that needs to display multiple fields in it's drop down. but only wants to store one. Right now it works to display multiple fields. but no matter what I select, the value shown in the textbox is "System.Data.DataRowView". Does anyone know why this is or how to fix it?
<ComboBox ItemsSource="{Binding Vwr.Table.Tbl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
x:Name="Supplier"
SelectedValuePath="Name"
SelectedValue="Name"
IsEditable="True" Style="{StaticResource tabTextBox}"
DataContext="{Binding parties}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SupplierChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}: {1}: {2}">
<Binding Path="Name"/>
<Binding Path="Funds"/>
<Binding Path="Terms"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Found the answer here :http://www.shujaat.net/2010/08/wpf-editable-combobox-with-datatemplate.html
The solution was to set TextSearch.TextPath. In my case, it was
TextSearch.TextPath="Name"
Updating my original code (unnecessary parts removed for brevity), the working version is here
<ComboBox
x:Name="Supplier"
ItemsSource="{Binding Vwr.Table.Tbl}"
SelectedValuePath="Name"
SelectedValue="Name"
IsEditable="True" Style="{StaticResource tabTextBox}"
TextSearch.TextPath="Name"
DataContext="{Binding parties}">
Hope this helps people out there!

Click event on custom control in list view not always setting SelectedItem

I have a situation where I have a listview :
<ListView ItemsSource="{Binding Environments}" SelectedItem="{Binding SelectedEnvironment}">
<ListView.ItemTemplate>
<DataTemplate>
<controls:RadioButtonTextBox DataContext="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
which uses a custom control as its item template:
<StackPanel Orientation="Horizontal">
<RadioButton VerticalAlignment="Center">
<RadioButton.IsChecked>
<MultiBinding Converter="{converters:StringCompareToBooleanConverter}">
<Binding Path="." RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=UserControl}"/>
<Binding Path="SelectedItem" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}"/>
</MultiBinding>
</RadioButton.IsChecked>
</RadioButton>
<TextBlock Text="{Binding}" VerticalAlignment="Center" Margin="5,0,0,0" Style="{DynamicResource RedTextBlock}"/>
</StackPanel>
And the problem I have is that if the user clicks on the textblock of the custom control then the correct thing happens, i.e. the item is selected (and the view model updates accordingly), yet if the user click on the radio button of the custom control the radio button becomes checked but the selected item is not updated and the previously selected item does not get deselected.
Can anyone help with this issue?
The click is handled and not propagated by your radio button.
In cases where the item contains input elements i tend to bind IsSelected in the ItemContainerStyle to IsKeyboardFocusWithin. Not sure if this deselects the old item, maybe only if selection mode is Single.
The simplest solution I found was to simply set the enabled flag on the radio button to false:
<StackPanel Orientation="Horizontal">
<RadioButton VerticalAlignment="Center" IsEnabled="False">
<RadioButton.IsChecked>
<MultiBinding Converter="{converters:StringCompareToBooleanConverter}">
<Binding Path="." RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=UserControl}"/>
<Binding Path="SelectedItem" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}"/>
</MultiBinding>
</RadioButton.IsChecked>
</RadioButton>
<TextBlock Text="{Binding}" VerticalAlignment="Center" Margin="5,0,0,0" Style="{DynamicResource RedTextBlock}"/>
</StackPanel>

Context sub menu with items source and additional items

I have a context menu in wpf. One of the items in the menu has a sub menu that gets populated from the ItemsSource of the header menu item. This sub menu is a list of commands that can be sent to another portion of the app. The list is basically a mru list restricted to 10 items. I want to add a separator and then a "More" option below the list of 10 items so the user can see the entire list of available commands. I can't seem to figure out how to add these extra items. I can get the list to populate dynamically from the ItemsSource of the parent menu item but I can't seem to figure out how to add the additional items to the bottom of the child menu. I don't want to put them in the items source and the "More" item needs to have its own command.
<MenuItem x:Name="ExecuteCommandMenuItem" Height="22" Style="{StaticResource RightClickMenuItemStyle}"
ItemsSource="{Binding Path=PanelCommands}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Panel Command" HorizontalAlignment="Left" Width="100"/>
</StackPanel>
</MenuItem.Header>
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem" BasedOn="{StaticResource RightClickMenuItemStyle}">
<Setter Property="MenuItem.Header" Value="{Binding}" />
<Setter Property="MenuItem.Command" Value="CommonCommands:CommandRepository.ExecutePanelCommand" />
<Setter Property="MenuItem.CommandParameter">
<Setter.Value>
<MultiBinding Converter="{CommonConverter:PanelCommandArgsConverter}">
<MultiBinding.Bindings>
<Binding Path="DataContext" RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}"/>
<Binding Path="Command" />
</MultiBinding.Bindings>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
Thanks.
<DataGrid x:Class="UICCNET.BaseControls.UserControls.BaseDataGrid"
Tag="{Binding RelativeSource={RelativeSource Self}, Path=Columns}">
<DataGrid.ContextMenu>
<ContextMenu Tag="{Binding RelativeSource={RelativeSource Self},Path=PlacementTarget.Tag}">
<MenuItem Header="Колонки">
<MenuItem >
<MenuItem.Template>
<ControlTemplate>
<ListBox Name="MyComboBox" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}},Path=Tag}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=Visibility, Mode=TwoWay, Converter={StaticResource BooleanToVisibilityConverter1}}" Content="{Binding Path=Header}"></CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ControlTemplate>
</MenuItem.Template>
</MenuItem>
</MenuItem>
<MenuItem Header="Друк"></MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
I dont think you can because it's bound to a source. So either add them to the source OR use a templateselector and do your logic in there. Define a normal template and then a "more" template.
Or, you can do some control nesting like
<menu>
<stackpanel>
<Menu Items>
</menu Items>
<break />
<Button>More</button>
</stackpanel>
</menu>
Sorry this is just off the top of my head. Can you post your XAML?

Categories

Resources