I have a list of objects as an ObservableCollection<MyObject>. I am already able to display the name property of these objects in a combobox using XAML within a DataGrid.
Now I have another object AnotherObject which has a property that is defined as a list of strings and each item of that list is the name property of MyObject mentioned above.
In the combobox I want to display the MyObject.name property preceeded by a checkbox.
Let's say that there are 30 items in the checkbox and an instance of AnotherObject.names holds three of them.
Now I want select the checkboxes of those items that are equal to the three items in AnotherObject.names.
How can I achieve this?
Some code:
MyObjectViewModel.cs:
public class MyObjectViewModel
{
private MyObject _myObject;
public MyObjectViewModel(MyObject myObject)
{
this._myObject = myObject;
}
public MyObject MyObject
{
get
{
return _myObject;
}
set
{
_myObject = value;
}
}
public string Name
{
get { return _myObject.Name; }
set
{
_myObject.Name = value;
}
}
public override string ToString()
{
return Name;
}
}
AnotherObjectRowViewmodel.cs:
public class AnotherObjectRowViewModel : INotifyPropertyChanged
{
private AnotherObject _anotherObject;
private ObservableCollection<MyObjectViewModel> _myObjects;
public AnotherObjectRowViewModel(AnotherObject anotherObject, ObservableCollection<MyObjectViewModel> myObjects)
{
this._anotherObject = anotherObject;
this._myObjects = myObjects;
}
public AnotherObject AnotherObject
{
get
{
return _anotherObject;
}
set
{
this._anotherObject = value;
}
}
public string Name
{
get { return _anotherObject.Name; }
set { _anotherObject.Name = value; }
}
public ObservableCollection<MyObjectViewModel> MyObjects {
get
{
return this._myObjects;
}
set
{
_myObjects = value;
}
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add
{
//throw new NotImplementedException();
}
remove
{
//throw new NotImplementedException();
}
}
}
That's what I tried in the XAML file:
<DataGridTemplateColumn x:Name="NamesColumn" Header="Names">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="Name" DataContext="{Binding}" ItemsSource="{Binding Path=myObjects}" IsEditable="True" IsReadOnly="True"
VerticalAlignment="Center" SelectionChanged="OnDetailParamsSelectionChanged" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="chbNames" Width="20" VerticalAlignment="Center" Checked="OnChbDetailParamsCheckBoxChecked" Unchecked="OnChbDetailParamsCheckBoxChecked"></CheckBox>
<TextBlock DataContext="{Binding Path=MyObject}" Text="{Binding Path=Name, Converter={StaticResource StringListConverter}}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
An example:
The combobox holds a list of 30 names (Name1, ..., Name30);
AnotherObject.names is { Name1, Name2, Name4, Name7 };
In the combobox the selected items shall be Name1, Name2, Name4, Name7. All other items shall stay unselected.
Update 2019-01-06:
This means that the Combobox's ItemsSource={Binding} is MyObject but the checked items shall be stored in AnotherObject. This is why I get this exception whenever I tick a checkbox:
System.Windows.Data Error: 40 : BindingExpression path error: 'xxx' property not found on 'object' ''MyObjectViewModel' (HashCode=34649765)'. BindingExpression:Path=xxx.DetailParams; DataItem='MyObjectViewModel' (HashCode=34649765); target element is 'CheckBox' (Name='chbDetailParams'); target property is 'IsChecked' (type 'Nullable`1')
My XAML contains thefollowing code snippet according to the IsItemsSelectedConverter:
<UserControl.Resources>
<ctb:IsItemSelectedConverter x:Key="IsItemSelectedConverter"/>
</UserControl.Resources>
The checkboxes IsChecked property looks like this:
IsChecked="{Binding Path=Names, Mode=TwoWay, Converter={StaticResource IsItemSelectedConverter}}"
but it doesn't work. Debugging this code, the IsItemsSelectedConverter is never used.
Create a Boolean property in the view model of MyObject, i.e. in MyObjectViewModel that will return a value if Name is in the list of names in AnotherObject.
Create an IsItemSelectedConverter and pass in the list of objects from your data model. Implement IValueConverter in a new class and bind the IsChecked property for each checkbox to two things: value should be the current item, and parameter should be the list of items. The converter should determine whether the current item is in the list of items and return the appropriate boolean.
You will have to create an instance of the Converter for the UI to use. I usually define converters in a separate Resource Dictionary that I make globally available for all XAML files.
My IsItemSelectedConverter looks like this:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is MyObjectViewModel objectViewModel && parameter is AnotherObjectRowViewModel anotherObjectRowViewModel)
{
return anotherObjectRowViewModel.MyObjects.Contains(objectViewModel);
}
return false;
}
On the XAML, my CheckBox code looks like this: <CheckBox IsChecked="{Binding Mode=OneWay, Converter={StaticResource converter}, ConverterParameter={StaticResource viewModel} }" />
Please note: there are a few problems with the code that will prevent it from running as is, but I'm assuming these are artifacts of copying and pasting your actual code.
Related
I have class DimensionType, it has properties Name, Id, etc. I constructed Property in my ViewModel = "dimStyleId" to retrieve selected form ComboBox. I am getting null in this property although I checked it in TextBlock and get it.
<!--Dimension Type Combobox-->
<ComboBox x:Name="DimensionType"
ItemsSource="{Binding dimTypes , Mode=TwoWay}"
SelectedValue="{Binding dimStyleId , Mode=TwoWay}"
SelectedValuePath="DimensionType"
DisplayMemberPath="Name"
Padding="3" />
and here is my VM Class
public class GridsDimViewModel : INotifyPropertyChanged
{
public ElementId dimensionType;
private ElementId _dimStyleId { get; set; }
public ElementId dimStyleId
{
get
{
return _dimStyleId;
}
set
{
if (_dimStyleId != value)
{
_dimStyleId = value;
NotifyPropertyChanged(nameof(dimStyleId));
}
}
}
}
and here is my check textbox which gets the id in it
<TextBlock Text="{Binding dimStyleId}"
Padding="3" />
Swap
SelectedValuePath="DimensionType"
to
SelectedValuePath="Id"
I have Comobox likes this
<dxe:ComboBoxEdit AutoComplete="True" IsTextEditable="False" ImmediatePopup="True" IncrementalFiltering="True" ItemsSource="{Binding Example}" />
In Vm
public List<object> Example
{
get { return example; }
set { example = value; OnPropertyChanged(new PropertyChangedEventArgs("Example")); }
}
public List<ArticlesStock> ArticlesStockList
{
get { return articlesStockList; }
set
{
articlesStockList = value;
OnPropertyChanged(new PropertyChangedEventArgs("ArticlesStockList"));
}
}
Example.Add(ArticlesStockList);
In ArticlesStock class, I have Prop Name Producer a string
How can I set this as my path in ComboBox?
normally we can just set this with props. but here I have a list .and inside I have one more list. in this list the first item value must be set.
C# Conversion how can I set this as Display member
((List<ArticlesStock>)Example[0])[0].WarehouseDeliveryNoteItem.Producer;
I would do the following: define a DataTemplate for your combobox's items and use a converter to retrieve the property you need.
DataTemplate definition:
<ComboBox ItemsSource="{Binding Example}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type List}">
<!--no Path is specified, which is equivalent to Path="."-->
<TextBlock Text="{Binding Converter={StaticResource MyConv}}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And the converter used to access the Producer property:
public class MyConv : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// here value will be an item of Example list, so a List<ArticlesStock>
var val = value as List<ArticlesStock>;
return val[0].Producer;
}
}
Note that I simplified your model structure for brevity.
What I'm trying to do
I'm trying to implement the AutoComplete feature on ComboBox without using WpfToolkit. If I'm not wrong, the ComboBox should support the AutoComplete for a simple string item so for example:
<ComboBox IsEditable="True">
<ComboBoxItem>Apple</ComboBoxItem>
<ComboBoxItem>Banana</ComboBoxItem>
<ComboBoxItem>Pear</ComboBoxItem>
<ComboBoxItem>Orange</ComboBoxItem>
</ComboBox>
Current object implementation
actually my ComboBox bound a custom object called CheckedListItem, this object have the following structure:
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
public CheckedListItem() { }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
this class show as ComboBoxItem a CheckBox near the Text of the Item, similar to:
[] Item1
[] Item2
...
where [] is the Checkbox.
ComboBox structure
The ComboBox look like this:
<ComboBox IsEditable="True" ItemSource={Binding Countries}/>
where Countries is a List of CheckedListItem<Country>, the object country is implemented in this way:
public class Country
{
public string Name { get; set; }
}
the problem on this code is that when I type some text in the ComboBox this doesn't do anything, but should display on the items that contains the string typed.
What I tried so far
I tried to fix this by implementing a PreviewTextInput event, actually I managed in this way:
MyComboBox.IsDropDownOpen = true;
CountryMenuComboBox.ItemsSource = Countries.Where(c => c.Item.Name.Contains(e.Text)).ToList();
but this doesn't working correctly, cause if I type "England", the ItemSource display even all Items.
Any idea to fix this?
Thanks.
Try to set the TextSearch.TextPath property to "Item.Name":
<ComboBox IsEditable="True" ItemsSource="{Binding Countries}"
TextSearch.TextPath="Item.Name">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox IsChecked="{Binding Item.IsChecked}" />
<TextBlock Text="{Binding Item.Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I am populating an ItemsControl with various elements, including Buttons and ComboBox elements. Accessing and populating the elements is simple, but I'm stuck on how to detect and associate which Item in the ItemsControl the ComboBox (or Button) belongs to.
To help illustrate the problem, consider the following basic UI:
Now, when I use the ComboBox or Button I want to be able to associate that use only with the ItemControl Item it's a part of. However, currently, if I select an item in the ComboBox every ComboBox in the ItemsControl will reflect that change.
I can capture the SelectedItem in the below ListBox, but ideally, I would like to be able to display both the SelectedItem and which ItemControl Item it came from. For instance, ComboBoxItem1, My First Property - From Item (1).
I am strictly adhering to MVVM principals, and consequently, I am not looking for any solutions using code-behind.
TL;DR
I know the code can become unwieldy. I believe the above description is adequate to state my problem, but I am including the basic boiler plate code below in case it's helpful in posting an answer. (Obviously, I have implemented INotifyProperty and ICommand elsewhere):
MainWindowView.xaml
<ItemsControl Width="300" Height="200" ItemsSource="{Binding MyObservableCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="2" Margin="10">
<StackPanel Margin="0,10,0,10">
<TextBlock Margin="10,0,0,0" Text="{Binding MyProperty}" FontWeight="Bold"/>
<ComboBox Width="270" Text="myBox" ItemsSource="{Binding DataContext.ComboOptions, RelativeSource={RelativeSource AncestorType=ItemsControl}}" DisplayMemberPath="ListItem" SelectedItem="{Binding DataContext.SelectedItem, RelativeSource={RelativeSource AncestorType=Window}}"/>
<RadioButton Width ="270" Content="Button1" Command="{Binding DataContext.GetButtonCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="Button1" Style="{DynamicResource {x:Type ToggleButton}}"/>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
MyComboBoxOptionsViewModel.cs
public class MyComboBoxOptionsViewModel : ObservableObject
{
private MyComboBoxOptionsModel _myComboBoxOptions = new MyComboBoxOptionsModel();
public MyComboBoxOptionsViewModel(MyComboBoxOptionsModel _myComboBoxOptions)
{
this._myComboBoxOptions = _myComboBoxOptions;
}
public string ComboBoxOption
{
get { return _myComboBoxOptions.ComboBoxOption; }
set
{
_myComboBoxOptions.ComboBoxOption = value;
RaisePropertyChangedEvent("ComboBoxOption");
}
}
}
MyComboBoxOptionsModel.cs
public class MyComboBoxOptionsModel
{
public string ComboBoxOption { get; set; }
}
MainWindowViewModel.cs
public class MainWindowViewModel : ObservableObject
{
private ObservableCollection<string> _messages = new ObservableCollection<string>();
private ObservableCollection<MyViewModel> _myObservableCollection = new ObservableCollection<MyViewModel>();
private List<MyComboBoxOptionsViewModel> _comboOptions = new List<MyComboBoxOptionsViewModel>();
private MyComboBoxOptionsViewModel _selectedItem = new MyComboBoxOptionsViewModel(null);
public MainWindowViewModel()
{
_myObservableCollection.Add(new MyViewModel(new MyModel { MyProperty = "My First Property" }));
_myObservableCollection.Add(new MyViewModel(new MyModel { MyProperty = "My Second Property" }));
_comboOptions.Add(new MyComboBoxOptionsViewModel(new MyComboBoxOptionsModel { ComboBoxOption = "Option1" }));
_comboOptions.Add(new MyComboBoxOptionsViewModel(new MyComboBoxOptionsModel { ComboBoxOption = "Option2" }));
_comboOptions.Add(new MyComboBoxOptionsViewModel(new MyComboBoxOptionsModel { ComboBoxOption = "Option3" }));
}
public MyComboBoxOptionsViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
_messages.Add(_selectedItem.ComboBoxOption);
RaisePropertyChangedEvent("SelectedItem");
}
}
public List<MyComboBoxOptionsViewModel> ComboOptions
{
get { return _comboOptions; }
set
{
if (value != _comboOptions)
{
_comboOptions = value;
RaisePropertyChangedEvent("ComboOptions");
}
}
}
public ObservableCollection<MyViewModel> MyObservableCollection
{
get { return _myObservableCollection; }
set
{
if (value != _myObservableCollection)
{
_myObservableCollection = value;
RaisePropertyChangedEvent("MyObservableCollection");
}
}
}
public ObservableCollection<string> Messages
{
get { return _messages; }
set
{
if (value != _messages)
{
_messages = value;
RaisePropertyChangedEvent("Messages");
}
}
}
}
I'm looking at the UI you want and think you basically need a main view model with a collection of item view models.
In that item view model create a command and a selected item property you can bind in your template to the combo box and button. That gives you a strict mvvm binding to a single instance of the combo box value and a command which is executed by the single instance of the button.
Your bindings for combo box items will then need an explicit source as part of the binding so you can hook into one collection of values from the main view model. Or add a collection to your item view model and keep it all nice a clean and together.
As you mention, you're code is very detailed - which is great - but I may have missed some other meaning from it.
Apologies if this is an answer to the wrong question :)
I have a combo box that is bound to a list of model objects. I've bound the combo box SelectedItem to a property that is the model type. All of my data binding works beautifully after the window has been loaded. The SelectedItem is set properly and I'm able to save the object directly with the repository.
The problem is when the window first loads I initialize the SelectedItem property and my combobox displays nothing. Before I moved to binding to objects I was binding to a list of strings and that worked just fine on initialization. I know I'm missing something but I can't figure it out.
Thanks in advance for any guidance you can provide.
(One note about the layout of this page. The combo boxes are actually part of another ItemTemplate that is used in a ListView. The ListView is bound to an observable collection in the main MV. Each item of this observable collection is itself a ModelView. It is that second ModelView that has the SelectedItem property.)
Here is my Model:
public class DistributionListModel : Notifier, IComparable
{
private string m_code;
private string m_description;
public string Code
{
get { return m_code; }
set { m_code = value; OnPropertyChanged("Code"); }
}
public string Name
{
get { return m_description; }
set { m_description = value; OnPropertyChanged("Name"); }
}
#region IComparable Members
public int CompareTo(object obj)
{
DistributionListModel compareObj = obj as DistributionListModel;
if (compareObj == null)
return 1;
return Code.CompareTo(compareObj.Code);
}
#endregion
}
Here the pertinent code in my ModelView:
public MailRoutingConfigurationViewModel(int agencyID)
: base()
{
m_agencyID = agencyID;
m_agencyName = DataManager.QueryEngine.GetAgencyName(agencyID);
IntializeValuesFromConfiguration(DataManager.MailQueryEngine.GetMailRoutingConfiguration(agencyID));
// reset modified flag
m_modified = false;
}
private void IntializeValuesFromConfiguration(RecordCheckMailRoutingConfiguration configuration)
{
SelectedDistributionList = ConfigurationRepository.Instance.GetDistributionListByCode(configuration.DistributionCode);
}
public DistributionListModel SelectedDistributionList
{
get { return m_selectedDistributionList; }
set
{
m_selectedDistributionList = value;
m_modified = true;
OnPropertyChanged("SelectedDistributionList");
}
}
And finally the pertinent XAML:
<UserControl.Resources>
<DataTemplate x:Key="DistributionListTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</UserControl.Resources>
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource}, Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False"
/>
#SRM, if I understand correctly your problem is binding your comboBox to a collection of objects rather than a collection of values types ( like string or int- although string is not value type).
I would suggest add a two more properties on your combobox
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource},
Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
SelectedValuePath="Code"
SelectedValue="{Binding SelectedDistributionList.Code }"/>
I am assuming here that DistributionListModel objects are identified by their Code.
The two properties I added SelectedValuePath and SelectedValue help the combobox identify what properties to use to mark select the ComboBoxItem by the popup control inside the combobox.
SelectedValuePath is used by the ItemSource and SelectedValue by for the TextBox.
don't call your IntializeValuesFromConfiguration from the constructor, but after the load of the view.
A way to achieve that is to create a command in your viewmodel that run this method, and then call the command in the loaded event.
With MVVM light toolkit, you can use the EventToCommand behavior... don't know mvvm framework you are using but there would probably be something like this.