I have a list of Roles that I want to have selectable in a DataGrid cell ComboBox.
I have the ObservableCollection Roles for the ComboBox being populated in the ViewModel.
For some reason, nothing shows up in the ComboBox though. What's the proper way of attaching this collection of objects?
Any comments or suggestions would be helpful.
PersonModel:
public int SelectedRole { get; set; }
RoleModel:
public int Id { get; set; }
public int Role { get; set; }
public string Description { get; set; }
public string RoleInfo
{
get
{
return $"{Role} - {Description}";
}
}
ViewModel:
private ObservableCollection<PersonModel> _people;
public ObservableCollection<PersonModel> People
{
get { return _people; }
set
{
_people = value;
NotifyOfPropertyChange(() => People);
}
}
public ObservableCollection<RoleModel> _roles;
public ObservableCollection<RoleModel> Roles
{
get
{
return _roles;
}
set
{
_roles = value;
NotifyOfPropertyChange(() => Roles);
}
}
public PersonViewModel(IEventAggregator events, IWindowManager windowmanager)
{
_events = events;
_windowManager = windowmanager;
sql = "SELECT * FROM People";
People = SqliteConnector.LoadData<PersonModel>(sql, new Dictionary<string, object>());
sql = "SELECT * FROM Roles";
Roles = SqliteConnector.LoadData<PersonModel>(sql, new Dictionary<string, object>());
}
View:
<DataGrid ItemsSource="{Binding Path=People}"
AutoGenerateColumns="False"
CanUserDeleteRows="True"
CanUserReorderColumns="True"
CanUserAddRows="True"
AlternatingRowBackground="#dfdfdf"
cm:Message.Attach="[Event RowEditEnding] = [Action SaveOrUpdate()]">
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Role}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="RoleInfo"
ItemsSource="{Binding Path=Roles}"
SelectedValue="{Binding Path=SelectedRole, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Role" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid>
The DataContext of the "row" elements of the DataGrid is the corresponding item of the ItemsSource collection, i.e. a PersonModel instance - which does of course not have a Roles property. You should have observed a corresponding data binding error message in the Output Window in Visual Studio.
In order to bind to the Roles property of the parent view model, use an expression like this:
ItemsSource="{Binding DataContext.Roles,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
The SelectedValue Binding could simply be like shown below, because PropertyChanged is already the default UpdateSourceTrigger for that property.
SelectedValue="{Binding Path=SelectedRole}"
When you run your program, check the output window to see exactly what kind of error is coming up for this control when it loads. This will give you a better idea of the exception type being thrown. My guess is that the ItemsSource of the ComboBox is looking for the ObservableCollection Roles in your Person Model, and is throwing an exception since Roles is in only in your ViewModel. Try moving this collection to the Person Model and assign it's initial value via the Person Model constructor.
Related
I have one class MovieSystem which inherits from AvailableMovies collection.
public class MovieSystem : IEnumerable<AvailableMovies >, IEnumerable
{
public AvailableMovies this[int i] { get; }
public AvailableMovies this[TheatreLocation location] { get; }
public AvailableMovies [] ReleasedMovies { get; }
}
TheatreLocation is an enum with values like
public enum TheatreLocation
{
Citycentre = 0,
CityNorth = 1,
CitySouth = 2,
CityEast = 3,
CityWest = 4,
}
AvailableMovies collection is with below properties
public class AvailableMovies
{
public AvailableMovies ();
public int Index { get; }
public TheatreLocation Location { get; set; }
public string TheatreID { get; set; }
public double Ticketprice{ get; }
}
My Xaml code looks like below
<ItemsControl ItemsSource="{Binding MovieSystem }">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path= ""What should I bind here? ""}"
DisplayMemberPath="{Binding Path = ???}"
SelectedIndex="{Binding Path =Location , Mode=TwoWay, Converter={StaticResource MovieSystemLocationConverter}}">
</ComboBox>
<ComboBox
ItemsSource="{Binding Path = ???}"
SelectedIndex="{Binding Path=TheatreID , Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="{Binding Path= ??}">
</ComboBox>
<TextBox Grid.Column="3"
Text="{Binding Path= Ticketprice, Mode =TwoWay,StringFormat=F3, NotifyOnValidationError=True}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have a view model, xaml datacontext is set to this view model.
internal class MovieSystemPanelViewModel
{
///tried this collection but not worked.
public ObservableCollection<LocationViewModel> Locations { get; } = new ObservableCollection<LocationViewModel>();
public MovieSystem MovieSystem { get; }
public MovieSystemPanelViewModel() {}
}
Now my problem is both ComboBox which should display Theatre id and location not displaying due to binding issue. If try to bind that Locations observable collection inside ComboBox itemsource its not allowing, inside items template only AvailableMovies properties are in bindable condition.
Ticketprice is binding properly and displaying.
The issue here is since you are binding Items.Control with a collection, child elements of that Items.Control will have access to that collection's property only. So, if you want to use another collection you need to use relative source to get access in datacontext level. And since it's an enum to properly display, you need to display the dictionary values using an extension method and also using another Itemtemplate inside ComboBox to get the proper display.
internal class MovieSystemPanelViewModel
{
///Use dictionary instead of observable collection
public Dictionary<TheatreLocation , string> Locations { get; } = new Dictionary<TheatreLocation , string>();
public MovieSystem MovieSystem { get; };
public MovieSystemPanelViewModel()
{
// Fill up data in dictionary
foreach (Enum item in Enum.GetValues(typeof(TheatreLocation)))
{
Locations.Add((TheatreLocation)item, ((TheatreLocation)item).GetDisplayName());
}
}
}
Write a Extension method in project somewhere, for display member according to requirement.
public static string GetDisplayName(this TheatreLocation location)
{
switch (location)
{
case TheatreLocation.CityCentre:
return "Center city location";
case TheatreLocation.CityNorth :
return "Northern city location";
case TheatreLocation.CitySouth :
return "Southern city location";
case TheatreLocation.CityEast :
return "Eastern city location";
case CameraLocation.CityWest :
return "Western city location";
default:
return "Unknown";
}
}
Now Bind xaml with these properties and methods.
<ComboBox
ItemsSource="{Binding Path= DataContext.Locations, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
SelectedIndex="{Binding Path =Location , Mode=TwoWay, Converter={StaticResource MovieSystemLocationConverter}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Value}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I'm having a bit of trouble to achieve the conversion of a "complex" ComboBox to an equally complex AutoCompleteBox. My goal is to be able to select and set a ShoppingCart's Item to be like one of the Items of a list. Here's the three steps to take to reproduce my situation (I'm using Stylet and its SetAndNotify() INPC method):
Create two objects, one having only a Name property and the other one having only the other object as a property
public class ItemModel : PropertyChangedBase
{
private string _name;
public string Name
{
get => _name;
set => SetAndNotify(ref _name, value);
}
}
public class ShoppingCartModel : PropertyChangedBase
{
public ItemModel Item { get; set; }
}
initialize and Populate both the ItemsList and the Shoppingcart in the DataContext (since we're using MVVM, it's the ViewModel)
public ShoppingCartModel ShoppingCart { get; set; }
public ObservableCollection<ItemModel> ItemsList { get; set; }
public ShellViewModel()
{
ItemsList = new ObservableCollection<ItemModel>()
{
new ItemModel { Name = "T-shirt"},
new ItemModel { Name = "Jean"},
new ItemModel { Name = "Boots"},
new ItemModel { Name = "Hat"},
new ItemModel { Name = "Jacket"},
};
ShoppingCart = new ShoppingCartModel() { Item = new ItemModel() };
}
Create the AutoCompleteBox, ComboBox, and a small TextBlock inside the View to test it all out:
<Window [...] xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=DotNetProjects.Input.Toolkit">
<!-- Required Template to show the names of the Items in the ItemsList -->
<Window.Resources>
<DataTemplate x:Key="AutoCompleteBoxItemTemplate">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Background="Transparent">
<Label Content="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<!-- AutoCompleteBox: can see the items list but selecting doesn't change ShoppingCart.Item.Name -->
<Label Content="AutoCompleteBox with ShoppingCart.Item.Name as SelectedItem:"/>
<toolkit:AutoCompleteBox ItemsSource="{Binding ItemsList}"
ValueMemberPath="Name"
SelectedItem="{Binding Path=ShoppingCart.Item.Name}"
ItemTemplate="{StaticResource AutoCompleteBoxItemTemplate}"/>
<!-- ComboBox: can see the items list and selecting changes ShoppingCart.Item.Name value -->
<Label Content="ComboBox with ShoppingCart.Item.Name as SelectedValue:"/>
<ComboBox ItemsSource="{Binding ItemsList}"
DisplayMemberPath="Name"
SelectedValue="{Binding Path=ShoppingCart.Item.Name}"
SelectedValuePath="Name"
SelectedIndex="{Binding Path=ShoppingCart.Item}" />
<!-- TextBox: Typing "Jean" or "Jacket" updates the ComboBox, but not the AutoCompleteBox -->
<Label Content="Value of ShoppingCart.Item.Name:"/>
<TextBox Text="{Binding Path=ShoppingCart.Item.Name}"/>
</StackPanel>
</window>
Changing the Binding Mode of the AutoCompleteBox's SelectedItem to TwoWay makes it print "[ProjectName].ItemModel" which means (I guess?) it's getting ItemModels and not strings, but I can't seem to make it work. Any help will be appreciated, thanks and feel free to edit my post if it's possible to make it smaller.
After a lot of attempts, I finally found the culprits :
INPC not implemented in ShoppingCartModel.Item despite the PropertyChangedBase inheritance (either implementing INPC or remove the PropertyChangedBase inheritance work)
public class ShoppingCartModel : PropertyChangedBase
{
private ItemModel _item;
public ItemModel Item
{
get => _item;
set => SetAndNotify(ref _item, value);
}
}
AutoCompleteBox's SelectedItem must be of the same type of ItemsSource, and have a TwoWay Mode Binding
<toolkit:AutoCompleteBox ItemsSource="{Binding ItemsList}"
ValueMemberPath="Name"
SelectedItem="{Binding Path=ShoppingCart.Item, Mode=TwoWay}"
ItemTemplate="{StaticResource AutoCompleteBoxItemTemplate}"/>
And finally... the most mysterious one is the ComboBox! Simply by being there it messes with the AutoCompleteBox and I have no idea why, just commenting the whole ComboBox makes it all work. If you know why the ComboBox breaks the AutoCompleteBox's binding feel free to help.
There's another problem though, when using an AutoCompleteBox inside a ListView, but it's better to create a separate question for that issue here
I have a few Problems with databinding in WPF.
I have a ListBox which has a binding to a BindingList.
<ListBox x:Name="SampleListBox" ItemsSource="{Binding List1}" ItemContainerStyle="{StaticResource ListBoxStyle}" BorderThickness="0" SelectedIndex="0" Margin="0">
<ListBox.ItemTemplate>
<DataTemplate >
<Border x:Name="border" Width="185">
<TextBlock Text="{Binding name}"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Till here, everything works fine. Now I have a DataGrid which should be linked to another BindingList and display some strings of it. So for example, if the first item of the ListBox is selected, the grid should show data of the first item of the second list.
I know, how it would work if both, the ListBox and the Grid get the data from the same list, but I have no idea, what to do, if this is not possible and there are two different lists.
You could bind SelectedIndex for the ListBox control to an property of type Int (Property1) in your ViewModel.
Also two-way bind SelectedItem in the DataGrid to another property (Property2) of the second list type.
In the setter for the Property1, change Property2 to be the item at the index of Property1 - i.e. List2[Property1]. It should change the selected item in the DataGrid.
So you want to use the listbox to, essentially, set a filter on the grid?
Note that LBItem and ViewModel below need to implement INotifyPropertyChanged and fire their PropertyChanged events when properties change, or none of this will work. But I'm leaving out the boilerplate for clarity.
Lots of ways to do that.
C#
public class LBItem {
public ViewModel Parent { get; set; }
public IEnumerable<String> SubItems {
get {
return Parent.List2.Where( /* filter items here */ );
}
}
}
public class ViewModel {
//
public ObservableCollection<LBItem> LBItems { get; set; }
public LBItem SelectedLBItem { get; set; }
public List<String> List2 { get; set; }
}
XAML
<ListBox
Name="MasterLB"
ItemsSource="{Binding LBItems}"
SelectedItem={Binding SelectedLBItem}"
/>
<DataGrid
ItemsSource="{Binding ElementName=MasterLB, Path=SelectedItem.SubItems}"
/>
That will work whether or not you bind MasterLB.SelectedItem to a property on the ViewModel. But as long as you are binding MasterLB.SelectedItem, you could just as easily bind DataGrid.ItemsSource to SelectedLBItem.SubItems on the ViewModel, like so:
<DataGrid
ItemsSource="{Binding Path=SelectedLBItem.SubItems}"
/>
But the ElementName binding is handy for a lot of things, so I'm giving you both.
You could also do something like this:
C#
public class LBItem {
public IEnumerable<String> Filter(IEnumerable<String> fullList) {
return fullList.Where( /* filter items here */ );
}
}
public class ViewModel {
public ObservableCollection<LBItem> LBItems { get; set; }
private LBItem _selectedItem;
public LBItem SelectedLBItem {
get { return _selectedItem; }
set {
_selectedItem = value;
List2Filtered = (null == _selectedItem)
? new List<String>()
: _selectedItem.Filter(List2).ToList();
}
}
public List<String> List2 { get; set; }
public List<String> List2Filtered { get; set; }
}
XAML
<ListBox
Name="MasterLB"
ItemsSource="{Binding LBItems}"
SelectedItem={Binding SelectedLBItem}"
/>
<DataGrid
ItemsSource="{Binding List2Filtered}"
/>
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 already have this issue but I cannot remember how to solved it. (I think it's related to the visual tree or the datacontext of the a contextMenu in wpf)
I have a ParentViewModel with a Combobox and a ContentPresenter.
The combobox display list of ChildViewModel. When one is selected it is displayed using the contentpresenter.
The ChildViewModel have a command to add items in a list. The command works find if it's binded on a button, but when it's done using a contextMenu the command is binded at the first execution but does not change if the ChildViewModel is changed (when another view model is selected in the combobox). The item is added to the previous selected ChildViewModel.
How can I solve this issue?
The Parent ViewModel:
public class Test1ViewModel:ObservableObject
{
public Test1ViewModel()
{
ViewModels = new ObservableCollection<TestViewModel>();
ViewModels.Add(new TestViewModel("View model1"));
ViewModels.Add(new TestViewModel("View model2"));
SelectedViewModel = ViewModels.FirstOrDefault();
}
private TestViewModel _selectedViewModel;
public TestViewModel SelectedViewModel
{
get { return _selectedViewModel; }
set
{
_selectedViewModel = value;
RaisePropertyChanged(() => SelectedViewModel);
}
}
public ObservableCollection<TestViewModel> ViewModels { get; set; }
}
The Parent View:
<StackPanel>
<ComboBox ItemsSource="{Binding ViewModels}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedViewModel}"></ComboBox>
<ContentPresenter Content="{Binding SelectedViewModel}"/>
</StackPanel>
The Child ViewModel:
public class TestViewModel : ObservableObject
{
private int _idx;
public TestViewModel(string vmName)
{
Name = vmName;
ListOfValues = new ObservableCollection<string>();
ListOfValues.Add("Value" + _idx++);
ListOfValues.Add("Value" + _idx++);
AddItemCommand = new DelegateCommand(() => ListOfValues.Add("Value" + _idx++));
}
public string Name { get; private set; }
public ObservableCollection<string> ListOfValues { get; set; }
public DelegateCommand AddItemCommand { get; private set; }
}
The Child View
<StackPanel>
<Button Content="AddValue" Command="{Binding AddItemCommand}"/> <!--Binding work when selected view model is changed-->
<TextBlock Text="{Binding Name}"/>
<ListBox ItemsSource="{Binding ListOfValues}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="AddValue" Command="{Binding AddItemCommand}"/> <!--Binding doesn't work when selected view model is changed-->
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
Thanks in advance
You are right.
The context menu is not in the visual tree, and only binds its datacontext once as the program is loaded.
In order to fix it, what I do is implement Josh Smith's Virtual Branch technique.
Look at this answer I posted to a similar question