Caliburn Micro Bindable collection - binding to combobox item source displays wrong text - wpf

I have this stupid problem. I bind from view model class property type of BindableCollection to the ItemSource property of ComboBox control.
Code from view model class:
public class SpiritUser
{
public string Nick { get; set; }
public string Password { get; set; }
}
public BindableCollection<SpiritUser> SpiritUsers
{
get { return _spiritUsers; }
set
{
_spiritUsers = value;
NotifyOfPropertyChange(() => SpiritUsers);
}
}
//constructor of view model class
public LogOnViewModel()
{
SpiritUsers = new BindableCollection<SpiritUser>
{
new SpiritUser
{
Nick = "Spirit_1",
Password = "slniecko1"
},
new SpiritUser
{
Nick = "Spirit_2",
Password = "slniecko1"
}
};
}
In view I have this:
Style on comboBox:
<Style x:Key="LogOnView_NickComboBox" TargetType="{x:Type ComboBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Label Content="{Binding Path=Nick}" Grid.Column="0" Grid.Row="0"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Height" Value="25"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="Margin" Value="10,4,10,4"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
ComboBox control:
<ComboBox ItemsSource="{Binding Path=SpiritUsers, Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource LogOnView_NickComboBox}"
SelectedValuePath="Nick"
Text="{Binding Path=Nick, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding Path=Nick, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEditable="True"/>
If I select some comboBox item I see Spirit.Models.SpiritUser instead of item text.
Problem cause if comboBox property IsEditable is set on true.
How can I solve this problem, I need bind property from view model on comboBox but also I need have comboBox editable and bind user input to property in view model.

In case of editable combobox use DisplayMemberPath property instead of ItemTemplate to specify what property of bound object you want to be displayed:
<ComboBox ItemsSource="{Binding Path=SpiritUsers, Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource LogOnView_NickComboBox}"
DisplayMemberPath="Nick"
SelectedValuePath="Nick"
Text="{Binding Path=CurrentUserNick, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
IsEditable="True"/>
In case if you still want to use ItemTemplate then you can specify what property of you object should be displayed in the text box via TextSearch.TextPath attached property:
<ComboBox ItemsSource="{Binding Path=SpiritUsers, Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource LogOnView_NickComboBox}"
SelectedValuePath="Nick"
TextSearch.TextPath="Nick"
Text="{Binding Path=CurrentUserNick, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
IsEditable="True"/>

Related

How to binding different data context to data template?

I try to simplify my Main user control that contains 8 user controls that are exactly the same but they are binding to different VM to display its data.
Currently, I have to create a template for each of my user control and binding to each of VM.
It seems that I can create one data template for all 8 user controls and apply the data template to each of the user control with different instance of VM.
Here are my code that current I have to use different templates for different dependency of View Model containing the data of each gauge
<DataTemplate x:Key="AnalogIO1Template" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue1VMDP.GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<DataTemplate x:Key="AnalogIO1Template" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue2VMDP.GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<Grid Background="#FFE3E2D7" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl x:Name="ucLinearGauge1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIO1Template }" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
<Grid Background="#FFE3E2D7" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl x:Name="ucLinearGauge2">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIO2Template }" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Here I try to create one Data template for all 8 user controls but it does not work
<DataTemplate x:Key="AnalogIOTemplate" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<Grid Background="#FFE3E2D7" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl Content="{Binding Path=GaugeValue1VMDP}" x:Name="ucLinearGauge1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIOTemplate}" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Is there a way to binding different data context to the data template?
Thanks
If class of UserControl, class of VM and bindings between them are identical and the only difference is instances of VM, creating a Style for UserControl and binding each instance of VM with DataContext of corresponding instance of UserControl would be enough.
Since we don't know actual code of your UserControl and VM, I will show this by samples.
Sample UserControl which has Id dependency property and can show its value:
<UserControl x:Class="WpfApp.SampleUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBlock x:Name="IdTextBlock"/>
</StackPanel>
</UserControl>
public partial class SampleUserControl : UserControl
{
public int Id
{
get { return (int)GetValue(IdProperty); }
set { SetValue(IdProperty, value); }
}
public static readonly DependencyProperty IdProperty =
DependencyProperty.Register("Id", typeof(int), typeof(SampleUserControl),
new PropertyMetadata(0, (d, e) => ((SampleUserControl)d).IdTextBlock.Text = e.NewValue.ToString()));
public SampleUserControl()
{
InitializeComponent();
}
}
Sample VM which has Id property and MainWindow's VM which has instances of sample VM:
// using Microsoft.Toolkit.Mvvm.ComponentModel;
public class SampleViewModel : ObservableObject
{
private int _id;
public int Id
{
get => _id;
set => SetProperty(ref _id, value);
}
}
public class MainWindowViewModel : ObservableObject
{
public SampleViewModel? VM1 { get; }
public SampleViewModel? VM2 { get; }
public MainWindowViewModel()
{
VM1 = new SampleViewModel { Id = 10 };
VM2 = new SampleViewModel { Id = 20 };
}
}
Finally, bind each instance of sample VM with DataContext of corresponding instance of sample UserControl so that Id of sample VM is bound with Id of sample UserControl.
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
Title="MainWindow"
Width="400" Height="200">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="{x:Type local:SampleUserControl}">
<Setter Property="Id" Value="{Binding Id}"/>
</Style>
</Window.Resources>
<StackPanel>
<local:SampleUserControl DataContext="{Binding VM1}"/>
<local:SampleUserControl DataContext="{Binding VM2}"/>
</StackPanel>
</Window>

Binding in a custom style

I'm using a DataGrid with several fixed columns.
Additionally, I'm dynamically adding columns which feature a custom style for their header, containing a CheckBox.
Here is the style:
<Style TargetType="DataGridColumnHeader" x:Key="CustomerColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" HorizontalAlignment="Center" Margin="3,0,3,0" Text="{Binding}"/>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
<CheckBox Margin="0,2,5,0"/>
<TextBlock HorizontalAlignment="Stretch" TextAlignment="Center" Text="Interpolate"/>
</StackPanel>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="0,1,1,1"/>
</Style>
This is how I add the columns and set their style:
grid.Columns.Add(new CustomDataGridTextColumn()
{
HeaderStyle = (Style)grid.Resources["CustomerColumnHeader"],
Header = "Test",
IsReadOnly = false,
Binding = new Binding(binding),
Interpolate = true
});
Edit: Here is what my Column class looks like:
class CustomDataGridTextColumn : DataGridTextColumn
{
public bool Interpolate { get; set; }
}
It's nothing more than a DataGridTextColumn with a property added.
How can I bind the IsChecked property of the style's CheckBox to the Interpolate property of my CustomDataGridTextColumn?
You could try binding from the Checkbox.IsChecked property to your custom column property using a RelativeSource Binding. Try this:
<CheckBox Margin="0,2,5,0" IsChecked="{Binding Interpolate, RelativeSource={
RelativeSource AncestorType={x:Type YourXamlPrefix:CustomDataGridTextColumn}}}" />
Clearly though, if this works at all, then this will only work when the Style is applied to an object that has a parent of type CustomDataGridTextColumn and you have correctly declared the YourXamlPrefix namespace to the CLR namespace of your custom column in the XAML.

Can I use a different Template for the selected item in a WPF ComboBox than for the items in the dropdown part?

I have a WPF Combobox which is filled with, say, Customer objects. I have a DataTemplate:
<DataTemplate DataType="{x:Type MyAssembly:Customer}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Address}" />
</StackPanel>
</DataTemplate>
This way, when I open my ComboBox, I can see the different Customers with their Name and, below that, the Address.
But when I select a Customer, I only want to display the Name in the ComboBox. Something like:
<DataTemplate DataType="{x:Type MyAssembly:Customer}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
Can I select another Template for the selected item in a ComboBox?
Solution
With help from the answers, I solved it like this:
<UserControl.Resources>
<ControlTemplate x:Key="SimpleTemplate">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</ControlTemplate>
<ControlTemplate x:Key="ExtendedTemplate">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Address}" />
</StackPanel>
</ControlTemplate>
<DataTemplate x:Key="CustomerTemplate">
<Control x:Name="theControl" Focusable="False" Template="{StaticResource ExtendedTemplate}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
<Setter TargetName="theControl" Property="Template" Value="{StaticResource SimpleTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</UserControl.Resources>
Then, my ComboBox:
<ComboBox ItemsSource="{Binding Customers}"
SelectedItem="{Binding SelectedCustomer}"
ItemTemplate="{StaticResource CustomerTemplate}" />
The important part to get it to work was Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}" (the part where value should be x:Null, not True).
The issue with using the DataTrigger/Binding solution mentioned above are two-fold. The first is you actually end up with a binding warning that you can't find the relative source for the selected item. The bigger issue however is that you've cluttered up your data templates and made them specific to a ComboBox.
The solution I present better follows WPF designs in that it uses a DataTemplateSelector on which you can specify separate templates using its SelectedItemTemplate and DropDownItemsTemplate properties as well as ‘selector’ variants for both.
Note: Updated for C#9 with nullability enabled and using pattern matching during the search
public class ComboBoxTemplateSelector : DataTemplateSelector {
public DataTemplate? SelectedItemTemplate { get; set; }
public DataTemplateSelector? SelectedItemTemplateSelector { get; set; }
public DataTemplate? DropdownItemsTemplate { get; set; }
public DataTemplateSelector? DropdownItemsTemplateSelector { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
var itemToCheck = container;
// Search up the visual tree, stopping at either a ComboBox or
// a ComboBoxItem (or null). This will determine which template to use
while(itemToCheck is not null
and not ComboBox
and not ComboBoxItem)
itemToCheck = VisualTreeHelper.GetParent(itemToCheck);
// If you stopped at a ComboBoxItem, you're in the dropdown
var inDropDown = itemToCheck is ComboBoxItem;
return inDropDown
? DropdownItemsTemplate ?? DropdownItemsTemplateSelector?.SelectTemplate(item, container)
: SelectedItemTemplate ?? SelectedItemTemplateSelector?.SelectTemplate(item, container);
}
}
To make it easier to use in XAML, I've also included a markup extension that simply creates and returns the above class in its ProvideValue function.
public class ComboBoxTemplateSelectorExtension : MarkupExtension {
public DataTemplate? SelectedItemTemplate { get; set; }
public DataTemplateSelector? SelectedItemTemplateSelector { get; set; }
public DataTemplate? DropdownItemsTemplate { get; set; }
public DataTemplateSelector? DropdownItemsTemplateSelector { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
=> new ComboBoxTemplateSelector(){
SelectedItemTemplate = SelectedItemTemplate,
SelectedItemTemplateSelector = SelectedItemTemplateSelector,
DropdownItemsTemplate = DropdownItemsTemplate,
DropdownItemsTemplateSelector = DropdownItemsTemplateSelector
};
}
And here's how you use it. Nice, clean and clear and your templates stay 'pure'
Note: 'is:' here is my xmlns mapping for where I put the class in code. Make sure to import your own namespace and change 'is:' as appropriate.
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MySelectedItemTemplate},
DropdownItemsTemplate={StaticResource MyDropDownItemTemplate}}" />
You can also use DataTemplateSelectors if you prefer...
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplateSelector={StaticResource MySelectedItemTemplateSelector},
DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />
Or mix and match! Here I'm using a template for the selected item, but a template selector for the DropDown items.
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MySelectedItemTemplate},
DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />
Additionally, if you don't specify a Template or a TemplateSelector for the selected or dropdown items, it simply falls back to the regular resolving of data templates based on data types, again, as you would expect. So, for instance, in the below case, the selected item has its template explicitly set, but the dropdown will inherit whichever data template applies for the DataType of the object in the data context.
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MyTemplate} />
Enjoy!
Simple solution:
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Address}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
(Note that the element that is selected and displayed in the box and not the list is not inside a ComboBoxItem hence the trigger on Null)
If you want to switch out the whole template you can do that as well by using the trigger to e.g. apply a different ContentTemplate to a ContentControl. This also allows you to retain a default DataType-based template selection if you just change the template for this selective case, e.g.:
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}"
Value="{x:Null}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- ... -->
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ComboBox.ItemTemplate>
Note that this method will cause binding errors as the relative source is not found for the selected item. For an alternate approach see MarqueIV's answer.
In addition to what said by H.B. answer, the Binding Error can be avoided with a Converter. The following example is based from the Solution edited by the OP himself.
The idea is very simple: bind to something that alway exists (Control) and do the relevant check inside the converter.
The relevant part of the modified XAML is the following. Please note that Path=IsSelected was never really needed and ComboBoxItem is replaced with Control to avoid the binding errors.
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Control}},
Converter={StaticResource ComboBoxItemIsSelectedConverter}}"
Value="{x:Null}">
<Setter TargetName="theControl" Property="Template" Value="{StaticResource SimpleTemplate}" />
</DataTrigger>
The C# Converter code is the following:
public class ComboBoxItemIsSelectedConverter : IValueConverter
{
private static object _notNull = new object();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// value is ComboBox when the item is the one in the closed combo
if (value is ComboBox) return null;
// all the other items inside the dropdown will go here
return _notNull;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I was going to suggest using the combination of an ItemTemplate for the combo items, with the Text parameter as the title selection, but I see that ComboBox doesn't respect the Text parameter.
I dealt with something similar by overriding the ComboBox ControlTemplate. Here's the MSDN website with a sample for .NET 4.0.
In my solution, I change the ContentPresenter in the ComboBox template to have it bind to Text, with its ContentTemplate bound to a simple DataTemplate that contains a TextBlock like so:
<DataTemplate x:Uid="DataTemplate_1" x:Key="ComboSelectionBoxTemplate">
<TextBlock x:Uid="TextBlock_1" Text="{Binding}" />
</DataTemplate>
with this in the ControlTemplate:
<ContentPresenter Name="ContentSite" IsHitTestVisible="False" Content="{TemplateBinding Text}" ContentTemplate="{StaticResource ComboSelectionBoxTemplate}" Margin="3,3,23,3" VerticalAlignment="Center" HorizontalAlignment="Left"/>
With this binding link, I am able to control the Combo selection display directly via the Text parameter on the control (which I bind to an appropriate value on my ViewModel).
I used next approach
<UserControl.Resources>
<DataTemplate x:Key="SelectedItemTemplate" DataType="{x:Type statusBar:OffsetItem}">
<TextBlock Text="{Binding Path=ShortName}" />
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Horizontal">
<ComboBox DisplayMemberPath="FullName"
ItemsSource="{Binding Path=Offsets}"
behaviors:SelectedItemTemplateBehavior.SelectedItemDataTemplate="{StaticResource SelectedItemTemplate}"
SelectedItem="{Binding Path=Selected}" />
<TextBlock Text="User Time" />
<TextBlock Text="" />
</StackPanel>
And the behavior
public static class SelectedItemTemplateBehavior
{
public static readonly DependencyProperty SelectedItemDataTemplateProperty =
DependencyProperty.RegisterAttached("SelectedItemDataTemplate", typeof(DataTemplate), typeof(SelectedItemTemplateBehavior), new PropertyMetadata(default(DataTemplate), PropertyChangedCallback));
public static void SetSelectedItemDataTemplate(this UIElement element, DataTemplate value)
{
element.SetValue(SelectedItemDataTemplateProperty, value);
}
public static DataTemplate GetSelectedItemDataTemplate(this ComboBox element)
{
return (DataTemplate)element.GetValue(SelectedItemDataTemplateProperty);
}
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uiElement = d as ComboBox;
if (e.Property == SelectedItemDataTemplateProperty && uiElement != null)
{
uiElement.Loaded -= UiElementLoaded;
UpdateSelectionTemplate(uiElement);
uiElement.Loaded += UiElementLoaded;
}
}
static void UiElementLoaded(object sender, RoutedEventArgs e)
{
UpdateSelectionTemplate((ComboBox)sender);
}
private static void UpdateSelectionTemplate(ComboBox uiElement)
{
var contentPresenter = GetChildOfType<ContentPresenter>(uiElement);
if (contentPresenter == null)
return;
var template = uiElement.GetSelectedItemDataTemplate();
contentPresenter.ContentTemplate = template;
}
public static T GetChildOfType<T>(DependencyObject depObj)
where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
}
worked like a charm. Don't like pretty much Loaded event here but you can fix it if you want
Yes. You use a Template Selector to determine which template to bind at run-time. Thus if IsSelected = False then Use this template, if IsSelected = True, use this other template.
Of Note:
Once you implement your template selector, you will need to give the templates keynames.
I propose this solution without DataTemplateSelector, Trigger, binding nor behavior.
The first step is to put the ItemTemplate (of the selected element) in the ComboBox resources and the ItemTemplate (of the item in the drop down menu) in the ComboBox.ItemsPanel resources and give both resources the same key.
The second step is to postpone the ItemTemplate resolution at run time by using both a ContentPresenter and a DynamicResource in the actual ComboBox.ItemTemplate implementation.
<ComboBox ItemsSource="{Binding Items, Mode=OneWay}">
<ComboBox.Resources>
<!-- Define ItemTemplate resource -->
<DataTemplate x:Key="ItemTemplate" DataType="viewModel:ItemType">
<TextBlock Text="{Binding FieldOne, Mode=OneWay}" />
</DataTemplate>
</ComboBox.Resources>
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Grid.IsSharedSizeScope="True"
IsItemsHost="True">
<StackPanel.Resources>
<!-- Redefine ItemTemplate resource -->
<DataTemplate x:Key="ItemTemplate" DataType="viewModel:ItemType">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="GroupOne" />
<ColumnDefinition Width="10" SharedSizeGroup="GroupSpace" />
<ColumnDefinition Width="Auto" SharedSizeGroup="GroupTwo" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding FieldOne, Mode=OneWay}" />
<TextBlock Grid.Column="2" Text="{Binding FieldTwo, Mode=OneWay}" />
</Grid>
</DataTemplate>
</StackPanel.Resources>
</StackPanel>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentPresenter ContentTemplate="{DynamicResource ItemTemplate}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

WPF CommandParameter not updated when set in Context Menu

I have a wpf window with several text box controls. I need to apply a common style that would apply a context menu to each control and i have defined it globally as follows,
<ContextMenu x:Key="textBoxMenu">
<Separator/>
<MenuItem Header="Affirm"
Command="{Binding Path=AffirmCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TextBox},AncestorLevel=1}}"/>
</ContextMenu>
<Style TargetType="{x:Type TextBox}" x:Key="TextBoxAffirmMenuStyle">
<Setter Property="ContextMenu" Value="{DynamicResource textBoxMenu}" />
</Style>
I Have used a Command to execute the appropriate method depending on the target of the context menu, which is in this case the text box.
To identify the controls uniquely, i have set the "Tag" property of each control with a unique string and i access this tag from the command parameter which is set to the target text box Control itself.
private bool CanAffirmExecute(object param)
{
string columnName = (param as FrameworkElement).Tag as string;
if (this.CheckIsAffirmed(columnName))
return true;
else
return false;
}
private void AffirmExecute(object param)
{
string columnName = (param as FrameworkElement).Tag as string;
this.Affirm(columnName);
}
The problem with this is that once the command parameter gets set to a particular control,
it will not change on subsequent context menu operations when right clicked on a different control. the Command parameter remains static and gets only the tag value set in the first control.
How can i get this to work so that i can access each of the tag values of the controls using the command?
thanks.
ContextMenu is at the root of its own visual tree, so any binding using RelativeSource.FindAncestor does not go past the ContextMenu.
A work around is to use a two stage binding with the PlacementTarget property as follows,
and to analyse the object parameter in the method OnAffirmCommand(object obj) to control your behaviour. In this case the object is the actual TextBox.
Here is the context menu definition:
<Window.Resources>
<ContextMenu x:Key="textBoxMenu">
<Separator/>
<MenuItem Header="Affirm"
Command="{Binding Path=AffirmCommand}"
CommandParameter="{Binding PlacementTarget.Tag,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
<Style TargetType="{x:Type TextBox}" x:Key="TextBoxAffirmMenuStyle">
<Setter Property="ContextMenu" Value="{StaticResource textBoxMenu}" />
</Style>
</Window.Resources>
Here are the text boxes:
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" ContextMenu="{StaticResource textBoxMenu}" Tag="{Binding RelativeSource={RelativeSource Self}}" Text="text in box 1"/>
<TextBox Grid.Row="1" ContextMenu="{StaticResource textBoxMenu}" Tag="{Binding RelativeSource={RelativeSource Self}}" Text="text in box 2"/>
<TextBox Grid.Row="2" ContextMenu="{StaticResource textBoxMenu}" Tag="{Binding RelativeSource={RelativeSource Self}}" Text="text in box 3"/>
</Grid>
Here is the command code from a ViewModel:
public class MainViewModel : ViewModelBase
{
public ICommand AffirmCommand { get; set; }
public MainViewModel()
{
AffirmCommand = new DelegateCommand<object>(OnAffirmCommand, CanAffirmCommand);
}
private void OnAffirmCommand(object obj)
{
}
private bool CanAffirmCommand(object obj)
{
return true;
}
}

WPF Usercontrol, TextBox PropertyChanged event not firing

I have a usercontrol with a DependencyProperty of Answer which is attached to a TextBox.
I have queried the database and bound the answer to the usercontrol and the correct value is displayed.
The issue occurs when I edit the TextBox, the PropertyChanged event is not firing and thus preventing me from saving the correct value back to the database.
Please see below my code.
Usercontrol:
<Grid>
<StackPanel>
<TextBlock Name="txtbQuestion" TextWrapping="Wrap" Text="Question" Foreground="Black" Margin="5" Style="{DynamicResource Label}" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Text}" ></TextBlock>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBox Name="txtAnswer" Margin="5" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Hidden" >
<TextBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cbMultiLine, Path=IsChecked}" Value="True">
<Setter Property="TextBox.TextWrapping" Value="Wrap" />
<Setter Property="TextBox.Height" Value="100" />
<Setter Property="TextBox.AcceptsReturn" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<CheckBox Name="cbMultiLine" Content="MultiLine" Margin="5" FontFamily="Georgia" Grid.Column="1" />
</Grid>
<Line Fill="Black" Margin="4" />
</StackPanel>
</Grid>
Usercontrol.cs:
public partial class ConditionQuestion : UserControl
{
public static readonly DependencyProperty AnswerProperty =
DependencyProperty.Register("Answer", typeof(string), typeof(ConditionQuestion), new UIPropertyMetadata(null, Answer_PropertyChanged));
public static readonly DependencyProperty QuestionProperty =
DependencyProperty.Register("Question", typeof(string), typeof(ConditionQuestion), new UIPropertyMetadata(null, Question_PropertyChanged));
public ConditionQuestion()
{
InitializeComponent();
}
public string Answer
{
get { return (string)GetValue(AnswerProperty); }
set { SetValue(AnswerProperty, value); }
}
public string Question
{
get { return (string)GetValue(QuestionProperty); }
set { SetValue(QuestionProperty, value); }
}
private static void Answer_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((ConditionQuestion)source).txtAnswer.Text = (string)e.NewValue;
}
private static void Question_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((ConditionQuestion)source).txtbQuestion.Text = (string)e.NewValue;
}
}
Declaring instance of Usercontrol:
<ListBox ItemContainerStyle="{StaticResource noSelect}" ItemsSource="{Binding Answer}"
Name="lbQuestions" BorderBrush="#E6E6E6" >
<ListBox.ItemTemplate>
<DataTemplate>
<my:ConditionQuestion Question="{Binding ConditionReportFormQuestions.Question}"
Answer="{Binding Answer, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I apologize in advance, I am {relatively} new to WPF. Can anyone see where I may be going wrong with this?
I have successfully got my other usercontrols binding and updating (this code is almost an exact copy) but the answers on them are ListBox selections where-as this usercontrol is binding to a TextBox.
Thanks in advance,
Kohan.
You have not bound the text box to the answer property. What you have done is put a changed handler on your answer property and when it is changed you manually set the text boxes text property.
Your code should look something like this
<TextBlock
Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:ConditionQuestion}}, Path=Answer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
this is a binding between the textbox and the property Answer on the class ConditionQuestion (the user control). Whenever the Answer property changes on the user control the text box will update and whenever you change the text in the textbox the Answer property will be updated. With this code you can remove your Answer_PropertyChanged method it is no longer neccessary. the binding takes care of it

Resources