I have two ComboBox. The first one's source is a dictionary, with strings as keys, and objects as values. Upon selecting an item, the second ComboBox will be populated with keys from the selected item's separate dictionary. When an item from the second ComboBox is selected, the TextBlock should show the value of the key that was chosen in the second ComboBox. However, the textblock always appears empty. I have made sure the value does have actual data in it, which leads me to believe it's a binding issue.
Here's the relevant sections of my ViewModel:
GPHDTModel gphdtModel = new GPHDTModel();
private Dictionary<string, object> models = new Dictionary<string, object>();
public Dictionary<string, object> Models
{
get
{
return models;
}
}
public MainWindowViewModel()
{
gphdtModel.MessageID = "3";
models.Add("GPHDT", gphdtModel);
}
Next here's the GPHDTModel:
private Dictionary<string, string> _fields = new Dictionary<string, string>();
public Dictionary<string, string> Fields
{
get
{
return _fields;
}
}
public GPHDTModel()
{
_fields.Add("MessageID", MessageID);
}
private string _messageID;
public string MessageID
{
get { return _messageID; }
set { _messageID = value; OnPropertyChanged("MessageID"); }
}
Finally the view:
<ItemsControl ItemsSource="{Binding DataModelCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<ComboBox x:Name="NMEAlist"
DisplayMemberPath="Key"
ItemsSource="{Binding Path=DataContext.Models,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ItemsControl}}}"
SelectedValuePath="Value" />
<ComboBox x:Name="ModelList"
DisplayMemberPath="Key"
ItemsSource="{Binding SelectedItem.Value.Fields,
ElementName=NMEAlist}"
SelectedValuePath="Value" />
<TextBlock Text="{Binding Value,
ElementName=ModelList}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit: Using a converter in the binding for the TextBlock to debug, it is showing the correct key, in this case "MessageID", but the value for the key is null, when it should be "3".
As #mm8 said below, when binding the textblock like this: Text="{Binding SelectedItem.Key, ElementName=ModelList}" "MessageID" appears in the textblock. So the binding is correct using SelectedItem.Value, but the value isn't being set correctly.
Try to bind to the Value property of the SelectedItem property of the ComboBox:
<TextBlock Text="{Binding SelectedItem.Value, ElementName=ModelList}" />
Related
In my view is a Listbox with a DataTemplate. The DataTemplate defines 2 columns, one for a TextBlock and one for a ComboBox.
The ListBox:
<ListBox ItemsSource="{Binding SelectedSensorProperty.ValueList}" Margin="5" Grid.Row="0"
ScrollViewer.VerticalScrollBarVisibility="Visible">
The ComboBox is defined as follows:
<ComboBox MinWidth="150"
ItemsSource="{Binding Path=DataContext.SelectedSensorProperty.PossibleValuesList,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
SelectedValuePath="Key"
DisplayMemberPath="Value"
SelectedValue="{Binding Path=Value, Mode=OneWay}"
Grid.Column="1"
Margin="2,0,0,0"/>
My ViewModel contains a variable SelectedSensorProperty of type SensorProperty.
public class SensorProperty
{
public string Name { get; set; }
// key = index value, value = index text
public Dictionary<string, string> PossibleValuesList { get; set; }
// key = feature value, value = index value
public Dictionary<string, string> ValueList { get; set; }
public SensorProperty()
{
ValueList = new Dictionary<string, string>();
}
}
Suppose ValueList contains three KeyValue pairs, then 3 comboxes are shown. The problem is that the selected combobox values are not written to the ValueList. Setting the binding of the combobox to TwoWay gives an error saying the Dictionary is readonly. How to solve this?
You either need to bind the ValuesList ListBox to the selected item of the PossibleValuesList, or catch the SelectionChanged event of the PossibleValuesList and then update the ValuesList dictionary. In both cases though, you need the dictionaries to notify the UI when something changed in them. That can be done by implementing the INotifyPropertyChanged interface, or the dictionaries to be an ObservableCollection instead.
Currently i have an ObservableCollection of MyClass in my Viewmodel. I use the getter of this Collection to fill it from an other Datasource. I can now Display this Data in a Window(Grid) and the correct Data is shown, but when i change the Data the set is not fired(I think it is because not the Collection is changed, only a Element in the Collection). Should i create a Property for every Property of MyClass, so i can react to the changes of a single Value, the Questions i ask myself are:
How do i know what Element is selected at the moment
How to fill the Collection correct when i have a binding to every single item
I also thought of a Event when my Collection is changed, but i am not sure how to implement it right.
public ObservableCollection<MyClass<string>> SelectedParams
{
get
{
//Fill the Collection
}
set
{
//I think here i want to react to changed Textboxvalues in my View
}
}
public class MyClass<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private T _curValue;
private string _value1;
private string _value2;
public string Value1
{
get
{
return _value1;
}
set
{
if (_value1 != value)
{
_value1 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value1)));
}
}
}
public string Value2
{
get
{
return _value2;
}
set
{
if (_value2 != value)
{
_value2 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value2)));
}
}
}
public T curValue
{
get
{
return _curValue;
}
set
{
_curValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(curValue)));
}
}
public MyClass()
{
}
public MyClass(string val1, string val2, T curVal)
{
Value1 = val1;
Value2 = val2;
curValue = curVal;
}
}
The xaml Code looks something like this
<ItemsControl ItemsSource="{Binding SelectedParams}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Value1}"/>
<Label Grid.Column="1" Content="{Binding Value2}"/>
<TextBox Grid.Column="2" Text="{Binding curValue, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit1: Changed MyClass to INotifyPropertyChanged now the Collection changes internal Values but the Setter is still not called on change of a Value
You need to implement INotifyPropertChanged interface for MyClass and raise the PropertyChanged in setter to notify UI that the property value changed.
How do i know what Element is selected at the moment
If you want support for item selection you have to use an other control. ItemsControl does not support selection.
Use ListView for example. Bind ItemsSource and SelectedItem to your class. Now every time you click on an item, SelectedValue is updated. And if you change SelectedValue from code the UI updates the selected item in the list. You can also bind other controls to SelectedValue like I did with the TextBlock outside the ListView.
View
<StackPanel>
<ListView ItemsSource="{Binding Values}" SelectedItem="{Binding SelectedValue}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Item1}" />
<TextBlock Text="=" />
<TextBlock Text="{Binding Path=Item2}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal">
<TextBox Text="Selected:" Background="DarkGray" />
<TextBox Text="{Binding SelectedValue.Item1, Mode=OneWay}" Background="DarkGray" />
</StackPanel>
</StackPanel>
Data
public class ListViewBindingViewModel : INotifyPropertyChanged
{
private Tuple<string,int> _selectedValue;
public ObservableCollection<Tuple<string,int>> Values { get; }
public Tuple<string, int> SelectedValue
{
get { return _selectedValue; }
set
{
_selectedValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedValue)));
}
}
public ListViewBindingViewModel()
{
Values = new ObservableCollection<Tuple<string, int>> {Tuple.Create("Dog", 3), Tuple.Create("Cat", 5), Tuple.Create("Rat",1)};
}
}
I want to bind my combo box ItemsSourse to the "value" i.e. (string component) of the
ObservableCollection<KeyValuePair<object, string>>.
How can I do that?
You could bind the ItemsSource to the ObservableCollection and then set the DisplayMemberPath to Value:
<ComboBox ItemsSource="{Binding YourCollection}" DisplayMemberPath="Value" />
The values in the combo box will then match the Values from the KeyValuePairs.
The easiest way to go would be to use the DisplayMemberPath property:
<ComboBox ItemsSource="{Binding Pairs}" DisplayMemberPath="Value" />
Alternatively, you could expose a new property in your viewmodel that will only contain the values. For example:
public ObservableCollection<string> AllValues { get; set; }
public ViewModel()
{
AllValues = new ObservableCollection<string>(Pairs.Select(x => x.Value));
}
<ComboBox ItemsSource="{Binding AllValues}" />
In my UserControl ucStep2 I have DataContext of Step2InfoData object that has several properties along with :
private string rockDensUnit;
public string RockDensity_Unit
{
get { return rockDensUnit; }
set
{
if (rockDensUnit != value)
{
rockDensUnit = value;
Changed("RockDensity_Unit");
}
}
}
In my app I got to bind several combo's with different normally measurement types Like {kg/m3, gm/m3}, {meter, cm} and so on such groups of measures. I mean, multiple combo's to have list of same items. So I preferred to create Class's of such lists that I can use in multiple combos. I created ComboItems.cs which contains all items lists that I will need to populate the drop down.
ComboItems.cs
//**OBJECTS I USE FOR LIST OF IEMS**
// Class for kg, gm
public class KgGmItems
{
public ObservableCollection<string> KgGmList { get; set; }
public KgGmItems()
{
KgGmList = new ObservableCollection<string>();
KgGmList.Add("kg/m3");
KgGmList.Add("gram/cm3");
}
public string ValueSelected { get; set; } // Don't know if this is useful in my case
}
// Class for meter, cm
public class MtCmItems : INotifyPropertyChanged
{
public MtCmItems()
{
Dict = new Dictionary<string, string>
{
{"meter", "meter"},
{"centimeter", "centimeter"}
};
}
//...
}
XML i.e. ucStep2 View
<!-- As the objects KgGmItems doesn't contain in ucStep2.xaml.cs or Step2InfoData (that is bound to this UC) so add reference of those classes -->
<UserControl.Resources>
<ObjectDataProvider x:Key="KgGmObj" ObjectType="{x:Type top:KgGmItems}" />
<ObjectDataProvider x:Key="MtCmObj" ObjectType="{x:Type top:MtCmItems}" />
</UserControl.Resources>
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" SelectedValue="{Binding Path=RockDensity_Unit, Mode=TwoWay}" SelectedIndex="0"
Background="#FFB7B39D" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="401,61,0,0" Name="comboBox6" VerticalAlignment="Top" Width="84" Visibility="Hidden">
</ComboBox>
I want to display ObservableCllection KgGmList items from KgGmItems class and bind the selected value to RockDensity_Unit of class Step2InfoData that is bound to this UserControl.
In the above combo, I am able to display all items in the drop down, also 1st item is selected by default. But the value is not bind to RockDensity_Unit; it's value remains null.
I want this to happen 2-way i.e. when RockDensity_Unit proeprtiy's value is set programmatically, the value should be selected in the drop down. Of course the value should exists in the list.
By default the 1st item should be selected.
UPDATE
Added DependencyProperty in ucStep2.xaml.cs
public static readonly DependencyProperty RockDensityUnitProperty =
DependencyProperty.Register("RockDensity_Unit", typeof(string), typeof(UserControl),
new FrameworkPropertyMetadata("kg/m3", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string RockDensity_Unit
{
get { return this.GetValue(RockDensityUnitProperty) as string; }
set { SetValue(RockDensityUnitProperty, value); }
}
XML
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" SelectedItem="{Binding Path=RockDensity_Unit, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ucStep2}}, Mode=TwoWay}"
Background="#FFB7B39D" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="401,61,0,0" Name="comboBox6" VerticalAlignment="Top" Width="84" Visibility="Hidden">
</ComboBox>
ERROR
Error 1 The type reference cannot find a public type named 'ucStep2'. Line 74 Position 194. This refers to the combobox ", "
after FindAncestor
DOUBT
The RockDensity_Unit CLR property in Step2InfoData is untouched.
Why is the code not able to find ucStep2 ? FYI, I think this may be relevant :
<UserControl x:Class="WellBore.ucStep2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WellBore.Models"
xmlns:top="clr-namespace:WellBore"
mc:Ignorable="d"
d:DesignHeight="870" d:DesignWidth="700" MaxHeight="970" MinHeight="700" MaxWidth="600">
Ok, so let's get this binding working... first, I am using an item from your KgGmItems class to bind to the ComboBox. In this class you have a collection of string values to display in the drop down and a string property to bind to the ComboBox.SelectedItem... perfect! Now I'm assuming that you have an instance of this class in the Resources section called KgGmObj... let's keep it simple to start with:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding ValueSelected, Mode=TwoWay}" />
This is all you need to setup the binding between the ComboBox and your class. One thing to note though, is that when you try to set the selected item from your code, it will only work if you set it to one of the actual items in the collection... I think that this doesn't really count when using strings, but it's important to know this anyway. If you were setting a custom class as the type of objects in the ComboBox instead, then you could set the selected item like this:
ValueSelected = KgGmList.Where(item => item.Name == "NameOfObjectToMatch").Single();
Or better like this if you had a uniquely identifiable property:
ValueSelected = KgGmList.Where(item => item.Id == Id).Single()
With your string values, you should be able to set the selected item from code like this:
ValueSelected = "Some value";
UPDATE >>> Ok, so let's have another go... I think that I may have enough information to go on now. I think that you want something like this:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding RockDensity_Unit, Mode=TwoWay}" />
The problem with this is that you have set the DataContext of the ComboBox to your KgGmObj object. This means that the Framework is going to try to find a property named RockDensity_Unit in that object. I also see another potential problem in your definition of this property.
In order to bind from a UserControl xaml to its code behind, you need to use a DependencyProperty. You can find out how to implement these from the Dependency Properties Overview page at MSDN. So first, I would recommend that you implement your RockDensity_Unit property as a DependencyProperty.
Next, we have to find a way to that property from the ComboBox in the xaml... we can do that using a RelativeSource binding like this:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding RockDensity_Unit, RelativeSource={RelativeSource Mode=
FindAncestor, AncestorType={x:Type ucStep2}}, Mode=TwoWay}" />
Now, if you have a DependencyProperty to bind to the SelectedItem property and your UserControl class is named ucStep2, this should all work... let me know how it goes.
UPDATE 2 >>>
Your error is because you have to add an XML namespace at the top of your XAML file... something like this:
xmlns:YourNamespace="clr-namespace:ApplicationName.FolderNameContainingClass"
Then you use it to reference your class like this:
...AncestorType={x:Type YourNamespace:ucStep2} ...
Also, in your DependencyProperty declaration, you're supposed to supply the name the type of your control, not UserControl, so change
Register("RockDensity_Unit", typeof(string), typeof(UserControl),
to
Register("RockDensity_Unit", typeof(string), typeof(NameOfYourUserControl),
Clearly... replace 'NameOfYourUserControl' with the actual name of your class that extends the UserControl.
Use a Dictionary.
XAML
<ComboBox ItemsSource="{Binding Dict}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Prop}"/>
Code Behind
public Dictionary< ValueType, string > Dict { get; private set; }
private ValueType _prop;
public ValueType Prop
{
get{ return _prop }
set
{
_prop = value;
NotifyPropertyChanged( "Prop" ); // Implement INotifyPropertyChanged
}
}
public ViewModel()
{
Dict = new Dictionary< ValueType, string >()
{
{ value1, string1 },
{ value2, string2 },
{ value3, string3 }
};
}
Hi all i have problem in this code, please help me..
I have view
<StackPanel Orientation="Horizontal" Margin="3">
<Label Content="Audit Type" MinWidth="100"/>
<Label Content=":"/>
<StackPanel Orientation="Vertical">
<ListBox ItemsSource="{Binding Items}" Margin="3" SelectionMode="Extended" MinWidth="180">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Name="check" Content="{Binding Value}" IsChecked="{Binding IsChecked, Mode=TwoWay}" Margin="3" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
and for View model
private List<AuditTypeExport> _items;
private List<string> _value;
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged("IsChecked");
}
}
public List<AuditTypeExport> Items
{
get { return _items; }
}
public List<string> Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
}
}
And ViewModel Constractor
_items = _model.GetAuditType();
_value = _model.GetAuditType().Select(item => item.Name).ToList();
For your information
public class AuditTypeExport
{
private int _id;
private string _name;
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
The result : checkbox appeares, but the content doesn't and I don't have a clue why.
Question Number 2 : I want to get the value back, how can I do that?
Thank you
It is unclear how you are using your ViewModel. Is that bound to the form? Or each item in the ListBox?
It looks like your ListBox is bound to the Items collection of your VM, so the ItemTemplate will be used with a AuditTypeExport as the data context. You are binding to "Value" and "IsChecked" properties which do not exist on the AuditTypeExport class.
What you are trying to do here is bind a property of type List<String> Value to CheckBox's Content property which is of type Object.
To simplify, you are assigning a collection of strings to a string. Which is not a good thing. And that is why it does not work.
Try using ItemsControl to show Value property or use an IValueConverter to convert List<String> to comma separated string.