WPF MVVM ContextMenu binding IsOpen to Model - c#

I have a button with a context menu associated to it. I can right click on the button and show the context menu as you would expect, however I want to be able to show the context menu after another event, such as a left click, or a drag and drop style event.
I am attempting to do this by binding the IsOpen property of the context menu to the view model, but this is not working as expected. On first left click of the button, nothing happens, although I can see the property on the view model that IsOpen is bound to being updated correctly.
If I right click, the menu will display correctly, and after this if I left click the menu will also show.
Has anyone ever seen this or have any ideas on what I need to do to get the contextMenu to open when the IsOpen property is updated?
XAML
<Window x:Class="PopUpTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mvp="clr-namespace:PopUpTest"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Window.DataContext>
<mvp:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.Resources>
<ContextMenu x:Key="Menu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}" IsOpen="{Binding PopupViewModel.IsOpen, Mode=TwoWay}">
<MenuItem Header="Delete" />
</ContextMenu>
</Grid.Resources>
<Button Command="{Binding DisplayPopupCommand}" ContextMenu="{StaticResource Menu}" Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}"/>
</Grid>
Code Behind
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Practices.Prism.Commands;
namespace PopUpTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MainWindowViewModel : BaseViewModel
{
private readonly PopupViewModel<ChildViewModel> _popupViewModel;
private readonly DelegateCommand _displayPopupCommand;
public MainWindowViewModel()
{
_popupViewModel = new PopupViewModel<ChildViewModel>(new ChildViewModel { FirstName = "John", LastName = "Doe" });
_displayPopupCommand = new DelegateCommand(() => { PopupViewModel.IsOpen = PopupViewModel.IsOpen == false; Console.WriteLine(PopupViewModel.IsOpen); });
}
public ICommand DisplayPopupCommand
{
get { return _displayPopupCommand; }
}
public PopupViewModel<ChildViewModel> PopupViewModel
{
get { return _popupViewModel; }
}
}
public class PopupViewModel<T> : BaseViewModel
{
private readonly T _data;
public PopupViewModel(T data)
{
_data = data;
}
public T Data
{
get { return _data; }
}
private bool _isOpen;
public bool IsOpen
{
get { return _isOpen; }
set
{
if (_isOpen != value)
{
_isOpen = value;
OnPropertyChanged("IsOpen");
}
}
}
}
public class ChildViewModel : BaseViewModel
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (_firstName != value)
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
if (_lastName != value)
{
_lastName = value;
OnPropertyChanged("LastName");
}
}
}
}
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

I have been able to solve this by introducing a BindingProxy to the XAML as described in the answer to this post on the MSDN forums:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/a4149979-6fcf-4240-a172-66122225d7bc/wpf-mvvm-contextmenu-binding-isopen-to-view-model?forum=wpf
The binding proxy gets around the issue where the ContextMenu does not have a DataContext until it first displays after a right click.
The issue is discussed further here:
http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

Related

How I can modify the Selected text of Combobox?

I'm newer to C# for UI WPF and I would like to set the value of Combobox?
cbx1.SelectedItem = "test 1";
This line show me every time null ( not an exception ) but the selectedItem is empty.
<telerik:RadComboBox Background="White" Foreground="{DynamicResource TitleBrush}" x:Name="cbx1" AllowMultipleSelection="True" VerticalAlignment="Center" HorizontalAlignment="Right" Width="200" Grid.Row="1" Grid.Column="0" Margin="0,14,11,14" Height="22"/>
Update :
I think that my question is not clear , I will give an example :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
ObservableCollection<Person> myPersonList = new ObservableCollection<Person>();
Person personJobs = new Person("Steve", "Jobs");
Person personGates = new Person("Bill", "Gates");
myPersonList.Add(personJobs);
myPersonList.Add(personGates);
MyComboBox.ItemsSource = myPersonList;
MyComboBox.SelectedItem = personGates;
}
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
}
In this code , if myCombobox.displayMemberPath = FirstName , How can I set the selected FirstName???
I do not have Teleriks Combobox, so I am using the standard Combobox here.
You should also know that XAML is very quiet, it do not throw an exception, but you will probably find an error message in the Output window.
This example is following the MVVM pattern. This means that you will find most of the code in the PersonViewModel class (the "View Model") where the properties are bound to the View. (I am not using the Model here, but that could be a data source class).
The PersonViewModel is inheriting the VMBase where the that notifies the View about changes in properties.
So, to your problem: I have added a property "SelectedPerson". This property is bound to the SelectedItem in the control. So each time you change the item in the combobox the SelectedPerson will contain the selected Person object.
I have also added a button "Select Steve" that will, In the PersonViewModel, find the Person object with firstname = "Steve" and set the SelectedPerson to that object. The Combobox will change to "Steve".
Window1.xaml
<Window x:Class="SO65149368.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SO65149368"
mc:Ignorable="d"
Title="Window1" Height="450" Width="800">
<StackPanel>
<ComboBox Name="MyComboBox" Width="300" HorizontalAlignment="Left" Margin="5"
ItemsSource="{Binding myPersonList}"
DisplayMemberPath="FirstName"
SelectedItem="{Binding SelectedPerson, Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<Button x:Name="SelectSteve" Content="Select Steve" Click="SelectSteve_Click"
Margin="5"/>
</StackPanel>
</StackPanel>
</Window>
Window1.xaml.cs
using System.Windows;
namespace SO65149368
{
public partial class Window1 : Window
{
public PersonViewModel PersonViewModel = new PersonViewModel();
public Window1()
{
DataContext = PersonViewModel;
InitializeComponent();
}
private void SelectSteve_Click(object sender, RoutedEventArgs e)
{
PersonViewModel.SelectSteve();
}
}
}
PersonViewModel.cs
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
namespace SO65149368
{
public class PersonViewModel : VMBase
{
public ObservableCollection<Person> myPersonList { get; } = new ObservableCollection<Person>();
public PersonViewModel()
{
Person personJobs = new Person("Steve", "Jobs");
Person personGates = new Person("Bill", "Gates");
myPersonList.Add(personJobs);
myPersonList.Add(personGates);
SelectedPerson = personGates;
//MyComboBox.ItemsSource = myPersonList;
//MyComboBox.SelectedItem = personGates;
}
public Person SelectedPerson
{
get { return _selectedPerson; }
set
{
Set(ref _selectedPerson, value);
Debug.WriteLine("Selected person: " + SelectedPerson.FirstName + " " + SelectedPerson.LastName);
}
}
private Person _selectedPerson;
public void SelectSteve()
{
SelectedPerson = myPersonList.FirstOrDefault(p => p.FirstName == "Steve");
}
}
}
VMBase.cs
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace SO65149368
{
public abstract class VMBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool Set<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
}
}

WPF MVVM: update datagrid based on selecteditem of another datagrid

Class Diagram
I am developing a wpf application using MVVM pattern.I want to update Second datagrid based on selection of first datagrid & if there is any change in Itemsource of second datagrid I want to update that change while the selection of first datarid is retained. Can anybody help me with this.
The need is more or less similar to this DataGrid SelectionChanged MVVM. But Whenever there is a update in the first datagrid collection automatically the data in second datagrid must be updated for the selected item of first datagrid.
O8Lwl.png
Based on your comments I've created an minimal example which shows the binding. I hope this is what you were looking for.
MainWindow.xaml and code behind
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="400" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="300*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<DataGrid Grid.Column="0" Grid.Row="0" Width="Auto" ItemsSource="{Binding DeviceList}" SelectedItem="{Binding SelectedDevice}" AutoGenerateColumns="True" />
<DataGrid Grid.Column="1" Grid.Row="0" Width="Auto" DataContext="{Binding SelectedDevice}" ItemsSource="{Binding Path=FaultList}" AutoGenerateColumns="True"/>
<Button Content="Trigger DataGrid 1 update" Grid.Column="0" Grid.Row="1" Margin="10,10,10,10" Width="Auto" Height="Auto" Click="Button_Click"/>
</Grid>
</Window>
// Code behind in MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow : Window
{
private MainViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new MainViewModel();
DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
vm.AddDevice();
}
}
}
MainViewModel and MainModel
using System;
using System.Collections.ObjectModel;
using System.Linq;
using WpfApp1.ViewModels;
namespace WpfApp1
{
public class MainViewModel : ViewModelBase<MainModel>
{
private DeviceViewModel selectedDevice;
public ObservableCollection<DeviceViewModel> DeviceList
{
get { return Model.DeviceList; }
}
public DeviceViewModel SelectedDevice
{
get { return selectedDevice; }
set
{
selectedDevice = value;
RaisePropertyChanged("SelectedDevice");
}
}
public MainViewModel() : base(new MainModel())
{
}
public void AddDevice()
{
int rnd = new Random().Next(1, 100);
if (!Model.DeviceList.Any(x => x.Name == $"Device_{rnd}"))
Model.DeviceList.Add(new DeviceViewModel($"Device_{rnd}"));
RaisePropertyChanged("DeviceList");
}
}
}
using System.Collections.ObjectModel;
using WpfApp1.ViewModels;
namespace WpfApp1
{
public class MainModel
{
private ObservableCollection<DeviceViewModel> deviceList;
public ObservableCollection<DeviceViewModel> DeviceList
{
get { return deviceList; }
set { deviceList = value; }
}
public MainModel()
{
deviceList = new ObservableCollection<DeviceViewModel>();
}
}
}
DeviceViewModel.cs and Device.cs
using System.Collections.ObjectModel;
using WpfApp1.Models;
namespace WpfApp1.ViewModels
{
public class DeviceViewModel : ViewModelBase<Device>
{
private Fault selectedFault = null;
public string Name
{
get { return Model.Name; }
set
{
Model.Name = value;
RaisePropertyChanged("Name");
}
}
public string SerialNumber
{
get { return Model.Id.ToString(); }
}
public ObservableCollection<FaultViewModel> FaultList
{
get { return Model.FaultList; }
}
public Fault SelectedFault
{
get { return selectedFault; }
set
{
selectedFault = value;
RaisePropertyChanged("SelectedFault");
}
}
public DeviceViewModel() : base(new Device())
{
FaultList.CollectionChanged += FaultList_CollectionChanged;
}
public DeviceViewModel(string name) : this()
{
Name = name;
for (int i = 0; i < 5; i++)
Model.FaultList.Add(new FaultViewModel() { Name = $"Fault_{i} of {name}" });
RaisePropertyChanged("FaultList");
}
private void FaultList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("FaultList");
}
}
}
using System;
using System.Collections.ObjectModel;
namespace WpfApp1.Models
{
public class Device
{
private string name = "";
private Guid id;
private ObservableCollection<FaultViewModel> faultList;
public string Name
{
get { return name; }
set { name = value; }
}
public Guid Id
{
get { return id; }
set { id = value; }
}
public ObservableCollection<FaultViewModel> FaultList
{
get { return faultList; }
set { faultList = value; }
}
public Device()
{
this.id = new Guid();
this.faultList = new ObservableCollection<FaultViewModel>();
}
public Device(string name) : this()
{
this.name = name;
}
}
}
FaultViewModel.cs and Fault.cs
using WpfApp1.Models;
namespace WpfApp1
{
public class FaultViewModel : ViewModelBase<Fault>
{
public string Name
{
get { return Model.FaultName; }
set
{
Model.FaultName = value;
RaisePropertyChanged("Name");
}
}
public string Id
{
get { return Model.FaultId.ToString(); }
}
public FaultViewModel() : base(new Fault())
{
}
}
}
using System;
namespace WpfApp1.Models
{
public class Fault
{
private Guid faultId;
private string faultName;
public Guid FaultId
{
get { return faultId; }
set { faultId = value; }
}
public string FaultName
{
get { return faultName; }
set { faultName = value; }
}
public Fault()
{
this.faultId = new Guid();
}
}
}
Last but not least: ViewModelBase.cs
using System.ComponentModel;
namespace WpfApp1
{
public class ViewModelBase<T> : INotifyPropertyChanged
{
T model;
public T Model { get { return model; } }
public ViewModelBase(T model)
{
this.model = model;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null && !string.IsNullOrEmpty(propertyName))
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
If you run the app you can click the button which simulates the update of device list in code behind. You then can select a device and the second DataGrid will show the FaultList of this device.
Old answer
Update: Related to your comments and the class diagram:
First thing:
I can only see one List in class Device. I assume this is the source for the first DataGrid? So you want to display properties of objects of type Fault in the 1st DataGrid. If so, where is the source for the second DataGrid? Or are you missing a Collection property in class Fault?
Second:
For data bindings you have to use ObservableCollection which implements INotifyCollectionChanged. You can not use a List<>.
Third:
Without seeing your code I can only guess what's going wrong. Let's assume class Device contains an ObservableCollection<Fault> FaultList and class Fault contains an ObservableCollection<string> FaultDetails. DataGrid 1 displays the list of faults and if you select one of them, DataGrid 2 displays some fault details. In your DeviceViewModel you would have the ObservableCollection<Fault> FaultList and a property Fault SelectedFault. Now FaultList has to be the ItemSource of the 1st DataGrid and SelectedFault has to be bound to the DataGrid.SelectedItem property. The ItemSource of Datagrid 2 has to be FaultDetails and the DataContext has to be SelectedFault. Maybe you need to propagate the change of the property.
I have not tested this! A minimal executable example that shows the problem would be great.
Old answer:
It's been a while since i wrote my last WPF application, but it could be that you either raise the NotifyPropertyChanged event for the 2nd DataGrid at the wrong place, or you raise it for the wrong Property.
DataGrid has a SelectionChanged event. Maybe you are able to access your VM from there and raise the correct PropertyChanged event for your 2nd DataGrid.

WPF - Force Binding on Invisible ComboBox

I have a WPF window that contains multiple user controls, some of which are invisible (Visibility = Hidden). One of these controls has a ComboBox that has an ItemsSource binding, and I want to preset its selected item while the window/control is loading.
However, it seems like the binding is not applied until the combobox is visible. When I go to set the SelectedItem property and I hit a breakpoint in the debugger, I notice that ItemsSource is null at that moment. Is there a way to force WPF to apply the data binding and populate the combobox while it stays invisible?
Reproducible Example:
MainWindow.xaml
<Window x:Class="HiddenComboBoxBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:HiddenComboBoxBinding"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Border x:Name="comboboxParent" Visibility="Collapsed">
<ComboBox x:Name="cmbSearchType" SelectedIndex="0" ItemsSource="{Binding SearchTypeOptions}" DisplayMemberPath="Name" SelectionChanged="cmbSearchType_SelectionChanged" />
</Border>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace HiddenComboBoxBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel viewModel { get; set; } = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel;
// Add some or all of our search types - in the real code, there's some business logic here
foreach (var searchType in SearchType.AllSearchTypes)
{
viewModel.SearchTypeOptions.Add(searchType);
}
// Pre-select the last option, which should be "Bar"
cmbSearchType.SelectedItem = SearchType.AllSearchTypes.Last();
}
private void cmbSearchType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HiddenComboBoxBinding
{
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<SearchType> SearchTypeOptions { get; set; } = new ObservableCollection<SearchType>();
#region INotifyPropertyChanged Members
private void NotifyPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class SearchType
{
// Source list of Search Types
private static List<SearchType> _AllSearchTypes;
public static List<SearchType> AllSearchTypes
{
get
{
if(_AllSearchTypes == null)
{
_AllSearchTypes = new List<SearchType>();
_AllSearchTypes.Add(new SearchType() { Name = "Foo" });
_AllSearchTypes.Add(new SearchType() { Name = "Bar" });
}
return _AllSearchTypes;
}
}
// Instance properties - for the purposes of a minimal, complete, verifiable example, just one property
public string Name { get; set; }
}
}
I was able to figure out the issue. Setting the SelectedItem actually did work (even though ItemsSource was null at that time) but in the XAML, the ComboBox had SelectedIndex="0" and it was taking precedence over the SelectedItem being set in the code-behind.

UserControlViewModel doesn't update binded property in UserControl

I want to be able to change a property in my main window from my user controls view model.
This is the connection
MainWindow
I bind my property from its view model to my usercontrol
MainWindowViewModel
My property lies here, it does get updated when user control property changes
UserControl1
its dependency property that's binded to Main Window View Model returns a value from UserControlViewModel
UserControl1ViewModel
The logic that changes the property (which is supposed to update MainWindowViewModel) lies here.
I can do the binding between all of them, but the problem is when I update my property from the bottom layer (UserControlViewModel), it does not update my property neither in UserControl or in my MainWindowViewModel.
Here is all my code (I have also uploaded the project on my google drive)
MainWindow.xaml
<Window x:Class="WpfApplicationViewToViewModel.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplicationViewToViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="367" Width="624">
<StackPanel>
<local:UserControl1 TextInUserControl="{Binding DataContext.TextInMainWindowViewModel,
Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
</local:UserControl1>
<Button Content="Test MainWindow VM" Command="{Binding CommandTestMWVM}" ></Button>
<Separator></Separator>
</StackPanel>
</Window>
MainVindow.xaml.cs
using System.Windows;
namespace WpfApplicationViewToViewModel
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
}
MainWindowViewModel.cs
using System;
using System.Windows;
using System.Windows.Input;
namespace WpfApplicationViewToViewModel
{
class MainWindowViewModel : ViewModelBase
{
public string TextInMainWindowViewModel
{
get
{
return _textInMainWindowViewModel;
}
set
{
_textInMainWindowViewModel = value;
RaisePropertyChanged("TextInMainWindowViewModel");
}
}
private string _textInMainWindowViewModel { get; set; }
//test button
public MainWindowViewModel()
{
_commandTestMWVM = new RelayCommand(new Action<object>(TestMWVM));
}
#region [Command] CommandTestMWVM
public ICommand CommandTestMWVM
{
get { return _commandTestMWVM; }
}
private ICommand _commandTestMWVM;
private void TestMWVM(object obj)
{
TextInMainWindowViewModel = TextInMainWindowViewModel + "MWVM";
MessageBox.Show("TextInMainWindowModel " + TextInMainWindowViewModel);
}
#endregion
}
}
UserControl1.xaml (includes just two buttons for testing purposes)
<UserControl x:Class="WpfApplicationViewToViewModel.UserControl1"
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:WpfApplicationViewToViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<Button Content="Test UC" Click="Button_Click"></Button>
<Button Content="Test UCVM" Command="{Binding CommandTestUCVM}" ></Button>
</StackPanel>
</UserControl>
UserControl1.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApplicationViewToViewModel
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
private UserControl1ViewModel VM = new UserControl1ViewModel();
public UserControl1()
{
InitializeComponent();
this.DataContext = VM;
//http://stackoverflow.com/questions/15132538/twoway-bind-views-dependencyproperty-to-viewmodels-property
//does not work because breaks binding somewhere
//string propertyInViewModel = "TextInUserControlViewModel";
//var bindingViewMode = new Binding(propertyInViewModel) { Mode = BindingMode.TwoWay };
//this.SetBinding(TextInUserControlProperty, bindingViewMode);
}
//dependency property declaration
public static DependencyProperty TextInUserControlProperty =
DependencyProperty.Register("TextInUserControl",
typeof(string),
typeof(UserControl1)
);
public string TextInUserControl
{
get {
return (DataContext as UserControl1ViewModel).TextInUserControlViewModel;
}
set
{
(DataContext as UserControl1ViewModel).TextInUserControlViewModel = value;
this.SetValue(TextInUserControlProperty, value);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
TextInUserControl = TextInUserControl + "UC";
MessageBox.Show("TextInUserControl : " + TextInUserControl);
}
}
}
UserControl1ViewModel.cs
using System;
using System.Windows;
using System.Windows.Input;
namespace WpfApplicationViewToViewModel
{
class UserControl1ViewModel : ViewModelBase
{
private string _textInViewModel;
public string TextInUserControlViewModel
{
get { return _textInViewModel; }
set {
_textInViewModel = value;
RaisePropertyChanged("TextInUserControlViewModel");
} }
//test button
public UserControl1ViewModel()
{
_commandTestUCVM = new RelayCommand(new Action<object>(TestUCVM));
}
#region [Command] CommandTestUCVM
public ICommand CommandTestUCVM
{
get { return _commandTestUCVM; }
}
private ICommand _commandTestUCVM;
private void TestUCVM(object obj)
{
TextInUserControlViewModel = TextInUserControlViewModel + "UCVM";
MessageBox.Show("TextInUserControlViewModel : " + TextInUserControlViewModel);
}
#endregion
}
}
Any help is really really appreciated because I've been trying to figure out this system (reading usercontrols viewmodel from mainwindow) for almost a week.
To make my question more clear:
TextInUserControl <=> TextInMainWindowViewModel : works succesfuly
TextInUserControl => TextInUserControlViewModel : works but when I change TextInUserControlViewModel, TextInUserControl doesn't get updated automatically.
Is there anyway I can let TextInUserControl know that TextInUserControlViewModel is changed?
You are setting your UserControl's DataContext to a UserControl1ViewModel instance, then binding the TextInUserControl property to DataContext.TextInMainWindowViewModel, which is resulting in it looking for the property UserControl1ViewModel.DataContext.TextInMainWindowViewModel, which does not exist.
One of the first rules of working with WPF/MVVM : NEVER set this.DataContext = x; in the code behind a user-control unless you intend to never pass that control any outside value.
Instead what you probably want is to add an instance of UserControl1ViewModel onto MainWindowViewModel, and bind the UserControl.DataContext to that instance.
For example,
class MainWindowViewModel : ViewModelBase
{
// add this property
public UserControl1ViewModel UserControlData { ... }
public string TextInMainWindowViewModel { ... }
public ICommand CommandTestMWVM { ... }
}
<!-- change binding to this -->
<local:UserControl1 DataContext="{Binding UserControlData}" />
and get rid of the following in your UserControl constructor
this.DataContext = VM;
You should call RaisePropertyChanged("TextInMainWindowViewModel"); in your MainWindowViewModel
I've fixed the problem by using a "bridge property". I copy the solution that might help the others having the same problem:
UserControl1.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApplicationViewToViewModel
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
this.DataContext = new UserControl1ViewModel();
/*
[Bridge Binding ©]
It's not possible to bind 3 properties.
So this bridge binding handles the communication
*/
string propertyInViewModel = "TextInUserControlViewModel";
var bindingViewMode = new Binding(propertyInViewModel);
bindingViewMode.Mode = BindingMode.TwoWay;
this.SetBinding(BridgeBetweenUCandVWProperty, bindingViewMode);
}
#region Bridge Property
public static DependencyProperty BridgeBetweenUCandVWProperty =
DependencyProperty.Register("BridgeBetweenUCandVW",
typeof(string),
typeof(UserControl1),
new PropertyMetadata(BridgeBetweenUCandVWPropertyChanged)
);
public string BridgeBetweenUCandVW
{
get
{
return (string)GetValue(BridgeBetweenUCandVWProperty);
}
set
{
this.SetValue(BridgeBetweenUCandVWProperty, value);
}
}
private static void BridgeBetweenUCandVWPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((UserControl1)d).TextInUserControl = (string)e.NewValue;
}
#endregion
#region TextInUserControl Property
public static DependencyProperty TextInUserControlProperty =
DependencyProperty.Register("TextInUserControl",
typeof(string),
typeof(UserControl1),
new PropertyMetadata(OnTextInUserControlPropertyChanged)
);
private static void OnTextInUserControlPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((UserControl1ViewModel)((UserControl)d).DataContext).TextInUserControlViewModel = (string)e.NewValue;
}
public string TextInUserControl
{
get {
return (string)GetValue(TextInUserControlProperty);
}
set
{
this.SetValue(TextInUserControlProperty, value);
}
}
#endregion
private void Button_Click(object sender, RoutedEventArgs e)
{
TextInUserControl += "[UC]";
MessageBox.Show("TextInUserControl : " + TextInUserControl);
}
}
}

ComboBox does not update when ItemsSource changes

I hav a ComboBox that is bound to a List<string>. When the List changes, the ComboBox does not, even though PropertyChanged was raised. When debugging, I found out that the List Property is even read.
The error can be reproduced using the following code:
XAML
<Window x:Class="ComboBoxTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="90" Width="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding Source, Mode=OneWay}"/>
<Button Grid.Column="1" Content="add string" Command="{Binding}" CommandParameter="Add"/>
</Grid>
</Window>
Code behind
using System.Windows;
namespace ComboBoxTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
}
ViewModel
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
namespace ComboBoxTest
{
class ViewModel : INotifyPropertyChanged, ICommand
{
public ViewModel()
{
Source = new List<string>();
Source.Add("Test1");
Source.Add("Test2");
Source.Add("Test3");
}
private List<string> _Source;
public List<string> Source
{
get { return _Source; }
set
{
_Source = value;
OnPropertyChanged("Source");
}
}
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public bool CanExecute(object parameter)
{
return true;
}
public event System.EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if ((string)parameter == "Add")
{
Source.Add("New string");
OnPropertyChanged("Source");
}
}
}
}
Why isn't the ComboBox updating?
The ComboBox does not update because it doesn't see any changes when it checks the List. The reference stays the same and the ComboBox is not informed about changes inside the List.
Refactoring the code to use ObservableCollection instead of List will solve the problem, because ObservableCollection implements INotifyCollectionChanged, what is necessary to inform the View about Changes inside an Object.

Categories