I am having a hard time finding the right syntax for binding to a ComboBox's SelectedItem's property. This is the XAML I am trying to use for the binding. Where you see SelectedItem.Mode is the idea I am having difficulty with. Note that CurrentMode is in the ViewModel and has the same type as SelectedItem.Mode
<ComboBox SelectedItem.Mode="{Binding Path=CurrentMode, Mode=TwoWays}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ImageSource}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<local:ModeItem Mode="Free" ImageSource="pencil.png"/>
<local:ModeItem Mode="Arrow" ImageSource="arrow.png"/>
</ComboBox>
A local:ModeItem looks like this
public class ModeItem : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty ModeProperty = DependencyProperty.Register("Mode", typeof(AnnotationMode), typeof(ModeItem));
public AnnotationMode Mode
{
get { return (AnnotationMode)GetValue(ModeProperty); }
set { SetValue(ModeProperty, value); }
}
public string ImageSource { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
I am using MVVM and trying to bind the AnnotationMode (CurrentMode) of the ViewModel to that of the ComboBox's SelectedItem's AnnotationMode (Mode)
Just do this
SelectedItem="{Binding CurrentMode}
You don't have to do all this extra stuff you are doing. Note You do need to make the datacontext of the combobox is pointing to your viewmodel.
Edit :-
You should be able to do this
SelectedValue="{Binding CurrentMode, Mode=TwoWay}"
SelectedValuePath="Mode"
Related
I have an ItemsControl that should display the values of some properties of an object.
The ItemsSource of the ItemsControl is an object with two properties: Instance and PropertyName.
What I am trying to do is displaying all the property values of the Instance object, but I do not find a way to set the Path of the binding to the PropertyName value:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Source=??{Binding Path=Instance}??, Path=??PropertyName??, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
the question marks are the points where I don't know how to create the binding.
I initially tried with a MultiValueConverter:
<TextBlock Grid.Column="1" Text="{Binding}">
<TextBlock.DataContext>
<MultiBinding Converter="{StaticResource getPropertyValue}">
<Binding Path="Instance" Mode="OneWay"/>
<Binding Path="PropertyName" Mode="OneWay"/>
</MultiBinding>
</TextBlock.DataContext>
</TextBlock>
The MultiValueConverter uses Reflection to look through the Instance and returns the value of the property.
But if the property value changes, this change is not notified and the displayed value remains unchanged.
I am looking for a way to do it with XAML only, if possible, if not I will have to write a wrapper class to for the items of the ItemsSource collection, and I know how to do it, but, since it will be a recurring task in my project, it will be quite expensive.
Edit:
For those who asked, InstanceProperties is a property on the ViewModel which exposes a collection of objects like this:
public class InstanceProperty : INotifyPropertyChanged
{
//[.... INotifyPropertyChanged implementation ....]
public INotifyPropertyChanged Instance { get; set; }
public string PropertyName { get; set; }
}
Obviously the two properties notify theirs value is changing through INotifyPropertyChanged, I don't include the OnPropertyChanged event handling for simplicity.
The collection is populated with a limited set of properties which I must present to the user, and I can't use a PropertyGrid because I need to filter the properties that I have to show, and these properties must be presented in a graphically richer way.
Thanks
Ok, thanks to #GazTheDestroyer comment:
#GazTheDestroyer wrote: I cannot think of any way to dynamically iterate and bind to an arbitrary object's properties in XAML only. You need to write a VM or behaviour to do this so you can watch for change notifications, but do it in a generic way using reflection you can just reuse it throughout your project
I found a solution: editing the ViewModel class InstanceProperty like this
added a PropertyValue property
listen to PropertyChanged event on Instance and when the PropertyName value changed is fired, raise PropertyChanged on PropertyValue
When Instance or PropertyName changes, save a reference to Reflection's PropertyInfo that will be used by PropertyValue to read the value
here is the new, complete, ViewModel class:
public class InstanceProperty : INotifyPropertyChanged
{
#region Properties and events
public event PropertyChangedEventHandler PropertyChanged;
private INotifyPropertyChanged FInstance = null;
public INotifyPropertyChanged Instance
{
get { return this.FInstance; }
set
{
if (this.FInstance != null) this.FInstance.PropertyChanged -= Instance_PropertyChanged;
this.FInstance = value;
if (this.FInstance != null) this.FInstance.PropertyChanged += Instance_PropertyChanged;
this.CheckProperty();
}
}
private string FPropertyName = null;
public string PropertyName
{
get { return this.FPropertyName; }
set
{
this.FPropertyName = value;
this.CheckProperty();
}
}
private System.Reflection.PropertyInfo Property = null;
public object PropertyValue
{
get { return this.Property?.GetValue(this.Instance, null); }
}
#endregion
#region Private methods
private void CheckProperty()
{
if (this.Instance == null || string.IsNullOrEmpty(this.PropertyName))
{
this.Property = null;
}
else
{
this.Property = this.Instance.GetType().GetProperty(this.PropertyName);
}
this.RaisePropertyChanged(nameof(PropertyValue));
}
private void Instance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == this.PropertyName)
{
this.RaisePropertyChanged(nameof(PropertyValue));
}
}
private void RaisePropertyChanged(string propertyname)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
#endregion
}
and here is the XAML:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Path=PropertyValue, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have this Custom Control
XAML:
<UserControl x:Class="WpfApplication1.UC"
...
x:Name="uc">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal">
<TextBox Text="{Binding Test, ElementName=uc}" Width="50" HorizontalAlignment="Left"/>
</StackPanel>
</UserControl>
C#
public partial class UC : UserControl
{
public static readonly DependencyProperty TestProperty;
public string Test
{
get
{
return (string)GetValue(TestProperty);
}
set
{
SetValue(TestProperty, value);
}
}
static UC()
{
TestProperty = DependencyProperty.Register("Test",typeof(string),
typeof(UC), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
public UC()
{
InitializeComponent();
}
}
And this is how i used that custom control:
<DockPanel>
<ItemsControl ItemsSource="{Binding Path=DataList}"
DockPanel.Dock="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" CommandParameter="{Binding}" Click="Button_Click"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
</DockPanel>
--
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private ObservableCollection<string> _dataList;
public ObservableCollection<string> DataList
{
get { return _dataList; }
set
{
_dataList = value;
OnPropertyChanged("DataList");
}
}
private string _selectedString;
public string SelectedString
{
get { return _selectedString; }
set
{
_selectedString = value;
OnPropertyChanged("SelectedString");
}
}
public MainWindow()
{
InitializeComponent();
this.DataList = new ObservableCollection<string>();
this.DataList.Add("1111");
this.DataList.Add("2222");
this.DataList.Add("3333");
this.DataList.Add("4444");
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.SelectedString = (sender as Button).CommandParameter.ToString();
}
}
If I do not change text of UC, everything is ok. When I click each button in the left panel, button's content is displayed on UC.
But when I change text of UC (ex: to 9999), Test property lost binding. When I click each button in the left panel, text of UC is the same that was changed (9999). In debug I see that SelectedString is changed by each button click but UC's text is not.
I can 'fix' this problem by using this <TextBox Text="{Binding Test, ElementName=uc, Mode=OneWay}" Width="50" HorizontalAlignment="Left"/> in the UC.
But I just want to understand the problem, can someone help me to explain it please.
Setting the value of the target of a OneWay binding clears the binding. The binding <TextBox Text="{Binding Test, ElementName=uc}" is two way, and when the text changes it updates the Test property as well. But the Test property is the Target of a OneWay binding, and that binding is cleared.
Your 'fix' works because as a OneWay binding, it never updates Test and the binding is never cleared. Depending on what you want, you could also change the UC binding to <local:UC Test="{Binding SelectedString, Mode=TwoWay}"/> Two Way bindings are not cleared when the source or target is updated through another method.
The issue is with below line
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
The mode is set as oneway for SelectString binding so text will be updated when the value from code base changes. To change either the source property or the target property to automatically update the binding source as TwoWay.
<local:UC Test="{Binding SelectedString, Mode=TwoWay}"/>
I have a user control that defines an ItemsControl and an ItemTemplate for that control, i.e.,
<ItemsControl Name="ItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Name="SelectionButton" Content="MyButton"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In the code behind I specify a dependency property that enables me to bind the ItemsSource property of the ItemsControl, i.e.,
public static readonly DependencyProperty ButtonSourceProperty = DependencyProperty.Register(
"ButtonSource", typeof(IEnumerable), typeof(MyControl),
new PropertyMetadata(null, new PropertyChangedCallback(OnButtonSourceChanged)));
public IEnumerable ButtonSource
{
get { return (IEnumerable)GetValue(ButtonSourceProperty); }
set { SetValue(ButtonSourceProperty, value); }
}
private static void OnButtonSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var buttonSelectionControl = (ButtonSelectionControl)d;
buttonSelectionControl.ItemsControl.ItemsSource = (IEnumerable)e.NewValue;
}
public static void SetButtonSource(DependencyObject obj, IEnumerable enumerable)
{
obj.SetValue(ButtonSourceProperty, enumerable);
}
public static IEnumerable GetButtonSource(DependencyObject obj)
{
return (IEnumerable)obj.GetValue(ButtonSourceProperty);
}
such that in xaml I can set the source for MyControl as follows
<local:MyControl ButtonSource={Binding MyCollection} \>
This works, but how can I define a dependency property in MyControl that specifies the command to bind to in MyCollection? Currently I have the following declared in xaml for the command binding
Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding .}"
How can I abstract this in such a way that I can set the item command to bind to in xaml, something like:
<local:MyControl ButtonSource={Binding MyCollection}
ButtonCommand={Binding MyCommand} \>
Pointers appreciated.
Ensure your UserControl has a dependency property to an ICommand, let's say this is called "ButtonCommand".
You should be able to bind to this inside the template for your control:
<ItemsControl Name="ItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Name="SelectionButton" Content="MyButton"
Command="{Binding ButtonCommand, RelativeSource={RelativeSource AncestorType=wpfApplication1:UserControl1}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The button click should then raise the command set in the "ButtonCommand" dependency property defined in your user control.
Your ButtonCommand would definition (inside the UserControl code) would look like this:
public static readonly DependencyProperty ButtonCommandProperty = DependencyProperty.Register("ButtonCommand", typeof (ICommand), typeof (UserControl1), new PropertyMetadata(default(ICommand)));
public ICommand ButtonCommand { get { return (ICommand) GetValue(ButtonCommandProperty); } set { SetValue(ButtonCommandProperty, value); }}
Creating a command class which implements ICommand is boilerplate stuff as you probably know. By putting this into your button xaml:
CommandParameter="{Binding}"
..it will allow you to work with the item from the list in the command handling code:
public class TheCommand : ICommand
{
public void Execute(object parameter)
{
var yourListItemObject = parameter as yourListItemObjectType;
}
// boilerplate stuff
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
You can define 2 dependency properties ButtonCommmand and ButtonCommandParameter in you UserContol's .cs file and bind them in UserControl's xaml like this:
<UserControl x:Class="..."
x:Name="this">
<Button Command="{Binding ButtonCommand, ElementName=this}"
CommandParameter="{Binding ButtonCommandPrameter, ElementName=this}"/>
</UserControl>
-------EDIT------
So, i figured that my code is correct and so are the code snippets from all of your answers. Thanks for that. My problem is that my dev-maschine runs .NET4.5 which behaves differently! The very same program (compiled against .NET4.0) runs correct on a maschine with .NET4.0 but not on a maschine with .NET4.5!
So here is my revised question.
-------EDIT------
First, the simple example how i two-way bind my combobox to my data context:
View model:
public class MainWindowViewModel
{
public List<String> MyElements { get; set; }
public string SelectedElement { get; set; }
public MainWindowViewModel()
{
MyElements = new List<string>() {"a", "b", "c"};
SelectedElement = "a";
}
}
and code-behind
private readonly MainWindowViewModel _viewModel = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _viewModel;
}
and my xaml
<ComboBox
ItemsSource="{Binding MyElements, Mode=OneWay}"
SelectedItem="{Binding SelectedElement}" />
This works fine and if i select an item whith the combobox, it is bound to my view model.
OK, now i want to make my viewModel static but still two-way bind the selectedItem. I try this:
public class MainWindowViewModel
{
public static List<String> MyElements { get; set; }
public static string SelectedElement { get; set; }
static MainWindowViewModel()
{
MyElements = new List<string>() {"a", "b", "c"};
SelectedElement = "a";
}
}
I do not need to set the datacontext in the Code-behind anymore and i know, that xaml needs an instance for two-way binding, so i have still the default constructor. I then bind the combobox
<Window.Resources>
<me:MainWindowViewModel x:Key="model"/>
</Window.Resources>
<StackPanel>
<ComboBox
ItemsSource="{Binding Source={x:Static me:MainWindowViewModel.MyElements}, Mode=OneWay}"
SelectedItem="{Binding Source={StaticResource model}, Path=SelectedElement}" />
</StackPanel>
The initial value is properly bound, but if i select another item with the combobox it it not reflected in my viewModel. What am i doing wrong?
EDIT:
If I use the exact same binding string for a TextBox and change the text in the box, it is reflected in the property.
<TextBox Text="{Binding Source={StaticResource model}, Path=SelectedElement}"/>
So obviously my binding string is correct but the way i use the combobox seems to be wrong. I also tried to bind SelectedValue instead... no change either.
Checked just in a sample project, works fine
public class ViewModel
{
static ViewModel()
{
Items=new ObservableCollection<string>();
SelectedItem = "222";
Items.Add("111");
Items.Add("222");
Items.Add("333");
Items.Add("444");
Items.Add("555");
}
private static string _selectedItem;
public static string SelectedItem
{
get { return _selectedItem; }
set { _selectedItem = value;
MessageBox.Show("Item " + value + " was selected");
}
}
private static ObservableCollection<string> _items;
public static ObservableCollection<string> Items
{
get { return _items; }
set { _items = value; }
}
}
and xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:my="clr-namespace:WpfApplication1"
Title="MainWindow" Height="200" Width="300">
<Grid>
<Grid.Resources>
<my:ViewModel x:Key="viewM"/>
</Grid.Resources>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="101,12,0,0" Name="comboBox1" VerticalAlignment="Top" Width="146"
ItemsSource="{Binding Source={x:Static my:ViewModel.Items}, Mode=OneWay}"
SelectedItem="{Binding Source={StaticResource viewM}, Path=SelectedItem}" />
</Grid>
</Window>
I have uploaded sample.
You're being confused between instances and static properties: you don't need to bind a static object.
<ComboBox
ItemsSource="{x:Static me:MainWindowViewModel.MyElements}"
SelectedItem="{x:Static me:MainWindowViewModel.SelectedElement}" />
And you should implement INotifyPropertyChanged nevertheless.
Binding is about resolving the right instance from which you want to fetch data.
If there is no meaning of instance, there is no need for binding.
here is my code:
xaml side:
I use a data template to bind with item "dataType1"
<DataTemplate DataType="{x:Type dataType1}">
<WrapPanel>
<CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}" Command="{Binding Path=CheckedCommand} />
<TextBlock Text="{Binding Path=ItemName, Mode=OneWay}" />
</WrapPanel>
</DataTemplate>
then I create a ComboBox with item with type "dataType1"
<ComboBox Name="comboBoxItems" ItemsSource="{Binding Path=DataItems, Mode=TwoWay}">
and here is dataType1 difinition:
class dataType1{public string ItemName{get; set;} public bool IsChecked {get; set;}}
the scenario is I prepare a list of dataType1 and bind it to the ComboBox, ItemName display flawlessly while CheckBox IsChecked value is always unchecked regardless the value of "IsChecked" in dataType1.
Is special handling needed in binding IsChecked property in CheckBox in wpf?
Peter Leung
The problem you're having here is that the CheckBox doesn't know when the value of dataType1.IsChecked changes. To fix that, change your dataType1 to:
class dataType1 : INotifyPropertyChanged
{
public string ItemName { get; set; }
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
if (isChecked != value)
{
isChecked = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So now, when the property value is changed it will notify the binding that it needs to update by raising the PropertyChanged event.
Also, there are easier ways to do this that avoid you having to write as much boiler-plate code. I use BindableObject from Josh Smith.