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 :)
Related
I have a ListView bound to a collection of objects (called Users, in this case), and the template includes a ContextActions menu. One of the menu items needs to be enabled or disabled depending on a condition having nothing directly to do with the items in the view (whether or not there's a Bluetooth connection to a certain kind of peripheral). What I'm doing right now is iterating the Cells in the TemplatedItems property and setting IsEnabled on each.
Here's the XAML for the ListView, stripped down to the parts that matter for my question:
<ListView ItemsSource="{Binding .}" ItemTapped="item_Tap">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Label}">
<TextCell.ContextActions>
<MenuItem
Text="Copy to other device"
ClassId="copyMenuItem"
Clicked="copyMenuItem_Click" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here's how I'm setting the property values now:
foreach (Cell cell in usersListView.TemplatedItems)
{
foreach (MenuItem item in cell.ContextActions)
{
if ("copyMenuItem" == item.ClassId)
{
item.IsEnabled = isBluetoothConnected;
}
}
}
That works, but i don't like it. It's obviously out of line with the whole idea of data-bound views. I'd much rather have a boolean value that I can bind to the IsEnabled property, but it doesn't make sense from an object design point of view to add that to the User object; it has nothing to do with what that class is about (representing login accounts). I thought of wrapping User in some local class that exists just to tape this boolean property onto it, but that feels strange also since the value will always be the same for every item in the collection. Is there some other way to bind the MenuItem.IsEnabled property?
Use relative binding
Get ready in your view model class, inherit INotifyPropertyChanged or your BaseViewModel.
public class YourViewModel : INotifyPropertyChanged
{
private string isBluetoothConnected;
public string IsBluetoothConnected
{
get => isBluetoothConnected;
set => SetProperty(ref isBluetoothConnected, value);
}
public ObservableCollection<User> Users { get; private set; }
}
Add a name to ListView for reference, and apply relative binding in MenuItem.
<ListView
x:Name="UserListView"
ItemsSource="{Binding Users}"
ItemTapped="item_Tap">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Label}">
<TextCell.ContextActions>
<MenuItem
IsEnabled="{Binding Path=BindingContext.IsBluetoothConnected, Source={x:Reference UserListView}}"
Text="Copy to other device"
ClassId="copyMenuItem"
Clicked="copyMenuItem_Click" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
It turns out that this case of BindableProperty is, in fact, not bindable: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/menuitem#enable-or-disable-a-menuitem-at-runtime
One must add a Command property to the MenuItem and assign a BindingContext to that, and set its executability. Here's the latest version of my code, which does work:
<MenuItem
Text="Copy to other device"
Clicked="copyMenuItem_Click"
BindingContext="{x:Reference usersListView}"
Command="{Binding BindingContext.CopyCommand}" />
public class UsersViewModel
{
public Command CopyCommand { get; set; }
public bool IsBluetoothConnected
{
get { return isBluetoothConnected; }
set
{
isBluetoothConnected = value;
if (CopyCommand.CanExecute(null) != value)
{
CopyCommand.ChangeCanExecute();
}
}
}
public ObservableCollection<User> Users { get; private set; }
private bool isBluetoothConnected;
public async System.Threading.Tasks.Task<int> Populate( )
{
CopyCommand = new Command(( ) => { return; }, ( ) => IsBluetoothConnected); // execute parameter is a no-op since I really just want the canExecute parameter
IList<User> users = await App.DB.GetUsersAsync();
Users = new ObservableCollection<User>(users.OrderBy(user => user.Username));
return Users.Count;
}
}
I'm still not entirely happy with this; it contaminates the view model with the concerns of a specific view. I'm going to see if I can separate the Command from the view model. But it does accomplish my primary goal, bringing this UI implementation into the data binding paradigm.
So I am trying to bind the following ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ListBoxItem> _PlacesOrCities;
public ObservableCollection<ListBoxItem> PlacesOrCities
{
get { return _PlacesOrCities; }
set { _PlacesOrCities = value; RaisePropertyChanged("PlacesOrCities"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ViewModel()
{
_PlacesOrCities = new ObservableCollection<ListBoxItem>();
}
}
To the following xaml:
<ListBox Name="lbPlacesCity" ItemsSource="{Binding Path=(gms:MainWindow.ViewModel).PlacesOrCities, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:ListBoxItem">
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Name}" Visibility="{Binding Visibility}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the codebehind as such:
public ViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new ViewModel();
DataContext = ViewModel;
}
And upon firing a button click event- I try to set the values of the observable collection using a in memory list:
private void StateProvince_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_CurrentSelectionPlaces = Canada.Provinces
.FirstOrDefault(x => x.Abbreviation == _SelectedStateProvince_ShortName)
.Place.OrderBy(x => x.Name).ToList();
foreach (var currentSelectionPlace in _CurrentSelectionPlaces)
{
ViewModel.PlacesOrCities.Add(currentSelectionPlace);
}
}
But it seems like none of the items are being added to the collection. Am I binding it incorrectly?
I've tried quite a few solutions but none of them seem to change the result- where no items in the list are being loaded into the collection properly.
EDIT:
It may be worth noting that the ListBoxItem as seen in the ViewModel is a custom model:
public class ListBoxItem
{
[J("Name")] public string Name { get; set; }
[J("PostalCodes")] public string[] PostalCodes { get; set; }
public Visibility Visibility { get; set; } = Visibility.Visible;
}
You should try to fit to the MVVM pattern, so the population of the list should occur at viewmodel level and not in the view's code behind.
You mentioned that you use a click event, instead of doing so, try to bind the command property of the button to a command in the viewmodel, see this link with an explanation of several types of commands and how to use them: https://msdn.microsoft.com/en-us/magazine/dn237302.aspx
In the other hand, if you already set the data context in the window constructor, to bind the ListBox items source you only need the name of the property to bind, "PlacesOrCities":
<ListBox Name="lbPlacesCity" ItemsSource="{Binding Path=PlacesOrCities, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:ListBoxItem">
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Name}" Visibility="{Binding Visibility}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It would also be recommendable trying to load the items in the list without any template, you can use ListBox DisplayMemberPath property to display the name, and once you are able to load items, apply the style.
Also in the way you use ObservableCollection, you actually need to replace the whole collection instead of adding to fire RaisePropertyChanged, try a normal property instead.
public ObservableCollection<ListBoxItem> PlacesOrCities {get;set;} = new ObservableCollection<ListBoxItem>();
Modifying the collection will update the UI, so whenever you use Add or Clear, the UI should know it.
Hope it helps.
In two combobox A and B.
A's ItemsSource is Custom list. and B's ItemsSource is UserControl list.
When manually setting the SelectedItem, A combobox works well, but B combobox UI do not show the selected Item. (In debugging, SelectedItem's value mapping is right, but the combobox B's UI do not be changed.)
All the other structure is same between A and B. What is the reason?
MainWindow.xaml
...
<ComboBox ItemsSource="{Binding FruitList}" SelectedItem="{Binding SelectedFruit}"
DisplayMemberPath="FruitName" />
<Button Content="Button" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ComboBox ItemsSource="{Binding UserControlList}" SelectedItem="{Binding SelectedUserControl}" DisplayMemberPath="ItemName" />
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click2"/>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
FruitList.Add(f1);
FruitList.Add(f2);
FruitList.Add(f3);
UserControlList.Add(u1);
UserControlList.Add(u2);
UserControlList.Add(u3);
}
Fruit f1 = new Fruit { FruitName = "Apple" };
Fruit f2 = new Fruit { FruitName = "Banana" };
Fruit f3 = new Fruit { FruitName = "Lemon" };
MyUserControl u1 = new MyUserControl { ItemName = "Apple" };
MyUserControl u2 = new MyUserControl { ItemName = "Banana" };
MyUserControl u3 = new MyUserControl { ItemName = "Lemon" };
ObservableCollection<Fruit> _FruitList = new ObservableCollection<Fruit>();
public ObservableCollection<Fruit> FruitList
{
get { return _FruitList; }
set
{
_FruitList = value;
OnPropertyChanged();
}
}
Fruit _SelectedFruit;
public Fruit SelectedFruit
{
get { return _SelectedFruit; }
set
{
_SelectedFruit = value;
OnPropertyChanged();
}
}
ObservableCollection<MyUserControl> _UserControlList = new ObservableCollection<MyUserControl>();
public ObservableCollection<MyUserControl> UserControlList
{
get
{
return _UserControlList;
}
set
{
_UserControlList = value;
OnPropertyChanged();
}
}
MyUserControl _SelectedUserControl;
public MyUserControl SelectedUserControl
{
get { return _SelectedUserControl; }
set
{
_SelectedUserControl = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.SelectedFruit = f3;
}
private void Button_Click2(object sender, RoutedEventArgs e)
{
this.SelectedUserControl = u3;
}
}
public class Fruit
{
public string FruitName { get; set; }
}
}
UserControl
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public string ItemName { get; set; }
}
This is not the good way of achieving this. Better define the ItemTemplate for the combobox to have the UserControl in it like:
<ComboBox ItemsSource="{Binding ItemList}" SelectedItem="{Binding SelectedItem}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<myControls:MyUserControl/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
and define the class Item
public class Item
{
public string ItemName { get; set; }
}
ObservableCollection<Item> _ItemsList = new ObservableCollection<Item>();
public ObservableCollection<Item> ItemsList
{
get
{
return _ItemsList ;
}
set
{
_ItemsList = value;
OnPropertyChanged();
}
}
Here DataContext of your UserControl will be Item object. you can bind the ItemName within you user control to show it in anyway you want.
in your user control you can have:
<TextBlock Text="{Binding ItemName}"></TextBlock>
Since you have asked "What is the reason?":
The reason why the second combo box does not show any selection is that ComboBox handles items of type ContentControl specially. In the read-only selection box, it is not the ContentControl that is used to display the value, but the content of the ContentControl. Since a UserControl is a ContentControl, the content of the UserControl is displayed inside the selection box, and therefore you have lost the data context of the UserControl; in the end, an empty string is displayed even though SelectedItem contains a reference to the UserControl that still has a valid data context. (As far as I know this behavior is undocumented; but you can see that it works like this by examining the ComboBox's code on http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/ComboBox.cs, especially the UpdateSelectionBoxItem() method).
By setting IsEditable="True" on the second ComboBox, you can see that everything works fine if the combo box has no read-only selection box.
Therefore, you generally should avoid adding UI elements to combo boxes, especially if you are using the DisplayMemberPath property, i.e. if you never want to actually display the UI element.
The recommended way to display ComboBox items with non-standard appearance (e.g. with UserControls) is described in the answer of #nit.
If you, however, insist on passing a UserControl item list to the ComboBox, you might remove DisplayMemberPath and use something like this:
<ComboBox ItemsSource="{Binding UserControlList}" SelectedItem="{Binding SelectedUserControl}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Furthermore, in the constructor of your UserControl, you must place this line:
((FrameworkElement) Content).DataContext = this;
This is necessary to make sure that the correct data context is available in the read-only selection box, which only contains the content of the user control, not the user control itself.
Please note, that with the above example, the drop-down list contains text only (i.e. the item names), but the selection box will contain the fully rendered user control.
I need to create an UI which allows me to select entries from one list box and add it to another listbox at the run time. Now, the listbox1 may contain combo box and checkbox as the items.
For example, if I add a combo box labelled Quarter with values "Q1, Q2, Q3, Q4" as an item in listbox1 and select the entry Q1 in it, and click on the "Add" button, it should be added to listbox2. Vice versa should also be possible. This should be possible at the run time. How could I add combo box and checkbox as an item to the listbox? Also, please suggest if for the add-remove buttons, the code I've is correct.
private void MoveListBoxItems(ListBox source, ListBox destination)
{
ListBox.SelectedObjectCollection sourceItems = source.SelectedItems;
foreach (var item in sourceItems)
{
destination.Items.Add(item);
}
while (source.SelectedItems.Count > 0)
{
source.Items.Remove(source.SelectedItems[0]);
}
}
private void button1_Click(object sender, EventArgs e)
{
MoveListBoxItems(listBox1, listBox2);
}
private void button2_Click(object sender, EventArgs e)
{
MoveListBoxItems(listBox2, listBox1);
}
This is a WPF solution to your need. I am posting it because you told me it could be useful for you. It largely surpasses anything you can ever hope to achieve in winforms, which is a very limited and outdated technology.
This is how it looks in my screen:
I am using some simple ViewModels to represent the data:
ListItemViewModel (the "base" one):
public class ListItemViewModel: ViewModelBase
{
private string _displayName;
public string DisplayName
{
get { return _displayName; }
set
{
_displayName = value;
NotifyPropertyChange(() => DisplayName);
}
}
}
BoolListItemViewModel (for CheckBoxes):
public class BoolListItemViewModel: ListItemViewModel
{
private bool _value;
public bool Value
{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged(() => Value);
}
}
}
SelectableListItemViewModel (for ComboBoxes):
public class SelectableListItemViewModel: ListItemViewModel
{
private ObservableCollection<ListItemViewModel> _itemsSource;
public ObservableCollection<ListItemViewModel> ItemsSource
{
get { return _itemsSource ?? (_itemsSource = new ObservableCollection<ListItemViewModel>()); }
}
private ListItemViewModel _selectedItem;
public ListItemViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NotifyPropertyChange(() => SelectedItem);
}
}
}
This is the "Main" ViewModel, which holds the 2 lists and the Commands (the Button actions)
public class ListBoxSampleViewModel: ViewModelBase
{
private ObservableCollection<ListItemViewModel> _leftItems;
public ObservableCollection<ListItemViewModel> LeftItems
{
get { return _leftItems ?? (_leftItems = new ObservableCollection<ListItemViewModel>()); }
}
private ObservableCollection<ListItemViewModel> _rightItems;
public ObservableCollection<ListItemViewModel> RightItems
{
get { return _rightItems ?? (_rightItems = new ObservableCollection<ListItemViewModel>()); }
}
private DelegateCommand<ListItemViewModel> _moveToRightCommand;
public DelegateCommand<ListItemViewModel> MoveToRightCommand
{
get { return _moveToRightCommand ?? (_moveToRightCommand = new DelegateCommand<ListItemViewModel>(MoveToRight)); }
}
private void MoveToRight(ListItemViewModel item)
{
if (item != null)
{
LeftItems.Remove(item);
RightItems.Add(item);
}
}
private DelegateCommand<ListItemViewModel> _moveToLeftCommand;
public DelegateCommand<ListItemViewModel> MoveToLeftCommand
{
get { return _moveToLeftCommand ?? (_moveToLeftCommand = new DelegateCommand<ListItemViewModel>(MoveToLeft)); }
}
private void MoveToLeft(ListItemViewModel item)
{
if (item != null)
{
RightItems.Remove(item);
LeftItems.Add(item);
}
}
}
This is the entire XAML for the Window:
<Window x:Class="WpfApplication4.Window14"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Title="Window14" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ListItemViewModel}">
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:BoolListItemViewModel}">
<CheckBox Content="{Binding DisplayName}" IsChecked="{Binding Value}" HorizontalAlignment="Left"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SelectableListItemViewModel}">
<ComboBox ItemsSource="{Binding ItemsSource}" SelectedItem="{Binding SelectedItem}"
HorizontalAlignment="Stretch" MinWidth="100"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding LeftItems}"
x:Name="LeftList"/>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<Button Content="Move to Right"
Command="{Binding MoveToRightCommand}"
CommandParameter="{Binding SelectedItem,ElementName=LeftList}"/>
<Button Content="Move to Left"
Command="{Binding MoveToLeftCommand}"
CommandParameter="{Binding SelectedItem,ElementName=RightList}"/>
</StackPanel>
<ListBox ItemsSource="{Binding RightItems}"
Grid.Column="2" x:Name="RightList"/>
</Grid>
</Window>
and finally, this is the Window Code-behind, which only initializes the ViewModel with some items:
public partial class Window14 : Window
{
public Window14()
{
InitializeComponent();
DataContext = new ListBoxSampleViewModel()
{
LeftItems =
{
new ListItemViewModel(){DisplayName = "Item1"},
new BoolListItemViewModel() {DisplayName = "Check Item 2", Value = true},
new SelectableListItemViewModel()
{
ItemsSource =
{
new ListItemViewModel() {DisplayName = "Combo Item 1"},
new BoolListItemViewModel() {DisplayName = "Check inside Combo"},
new SelectableListItemViewModel()
{
ItemsSource =
{
new ListItemViewModel() {DisplayName = "Wow, this is awesome"},
new BoolListItemViewModel() {DisplayName = "Another CheckBox"}
}
}
}
}
}
};
}
}
At first glance, this might seem like a LOT of code... but if you take 2 seconds to analyze it... Its just "simple, simple properties and INotifyPropertyChanged. That's how you program in WPF.
I'm talking about a completely different paradigm from what you might be used to in winforms, but it's really worth the effort of learning it. Notice that nowhere in my code I am interacting with UI elements. I just create the ViewModel structure and let the WPF Binding System to take care of generating the UI for me, using the provided DataTemplates.
I'm using the ViewModelBase from MVVM Light and the DelegateCommand from WPFTutorial.net. You can copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself (you will also need these 2 classes from the links above)
If you need to integrate this in an existing winforms application, you will need the ElementHost
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