Trouble copying a Grid object from one TabItem to another - c#

In my program I have tabItems that have their commands bound to a View Model. I am in the process of implementing a function that will copy the design structure of a "master" tabItem, along with it's command functionality in order to create a new tabItem. I need to do this because the user of this program will be allowed to add new tabItems.
Currently I am using the question Copying a TabItem with an MVVM structure, but I seem to be having trouble when the function tries to copy the Grid object using dependencyValue.
The class I am using:
public static class copyTabItems
{
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
}
When dependencyValue gets to {[Content, System.Windows.Controls.Grid]} the program throws an InvalidOperationException was Unhandled stating that, "Specified element is already the logical child of another element. Disconnect it first".
What does this mean? Is this a common problem with the Grid in WPF (am I breaking some rule by trying to do this?)? Is there something in my program that I am not aware of that is causing this?

Ok. This is how you're supposed to deal with a TabControl in WPF:
<Window x:Class="MiscSamples.MVVMTabControlSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MiscSamples"
Title="MVVMTabControlSample" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Tab1ViewModel}">
<!-- Here I just put UI elements and DataBinding -->
<!-- You may want to encapsulate these into separate UserControls or something -->
<StackPanel>
<TextBlock Text="This is Tab1ViewModel!!"/>
<TextBlock Text="Text1:"/>
<TextBox Text="{Binding Text1}"/>
<TextBlock Text="Text2:"/>
<TextBox Text="{Binding Text2}"/>
<CheckBox IsChecked="{Binding MyBoolean}"/>
<Button Command="{Binding MyCommand}" Content="My Command!"/>
</StackPanel>
</DataTemplate>
<!-- Here you would add additional DataTemplates for each different Tab type (where UI and logic is different from Tab 1) -->
</Window.Resources>
<DockPanel>
<Button Command="{Binding AddNewTabCommand}" Content="AddNewTab"
DockPanel.Dock="Bottom"/>
<TabControl ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTab}"
DisplayMemberPath="Title">
</TabControl>
</DockPanel>
</Window>
Code Behind:
public partial class MVVMTabControlSample : Window
{
public MVVMTabControlSample()
{
InitializeComponent();
DataContext = new MVVMTabControlViewModel();
}
}
Main ViewModel:
public class MVVMTabControlViewModel: PropertyChangedBase
{
public ObservableCollection<MVVMTabItemViewModel> Tabs { get; set; }
private MVVMTabItemViewModel _selectedTab;
public MVVMTabItemViewModel SelectedTab
{
get { return _selectedTab; }
set
{
_selectedTab = value;
OnPropertyChanged("SelectedTab");
}
}
public Command AddNewTabCommand { get; set; }
public MVVMTabControlViewModel()
{
Tabs = new ObservableCollection<MVVMTabItemViewModel>();
AddNewTabCommand = new Command(AddNewTab);
}
private void AddNewTab()
{
//Here I just create a new instance of TabViewModel
//If you want to copy the **Data** from a previous tab or something you need to
//copy the property values from the previously selected ViewModel or whatever.
var newtab = new Tab1ViewModel {Title = "Tab #" + (Tabs.Count + 1)};
Tabs.Add(newtab);
SelectedTab = newtab;
}
}
Abstract TabItem ViewModel (you to derive from this to create each different Tab "Widget")
public abstract class MVVMTabItemViewModel: PropertyChangedBase
{
public string Title { get; set; }
//Here you may want to add additional properties and logic common to ALL tab types.
}
TabItem 1 ViewModel:
public class Tab1ViewModel: MVVMTabItemViewModel
{
private string _text1;
private string _text2;
private bool _myBoolean;
public Tab1ViewModel()
{
MyCommand = new Command(MyMethod);
}
public string Text1
{
get { return _text1; }
set
{
_text1 = value;
OnPropertyChanged("Text1");
}
}
public bool MyBoolean
{
get { return _myBoolean; }
set
{
_myBoolean = value;
MyCommand.IsEnabled = !value;
}
}
public string Text2
{
get { return _text2; }
set
{
_text2 = value;
OnPropertyChanged("Text2");
}
}
public Command MyCommand { get; set; }
private void MyMethod()
{
Text1 = Text2;
}
}
Edit: I forgot to post the Command class (though you surely have your own)
public class Command : ICommand
{
public Action Action { get; set; }
public void Execute(object parameter)
{
if (Action != null)
Action();
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
Action = action;
}
}
And finally PropertyChangedBase (just a helper class)
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:
Basically, each Tab Item type is a Widget, which contains its own logic and Data.
You define all logic and data at the ViewModel or Model level, and never at the UI level.
You manipulate the data defined in either the ViewModel or the Model level, and have the UI updated via DataBinding, never touching the UI directly.
Notice How I'm leveraging DataTemplates in order to provide a specific UI for each Tab Item ViewModel class.
When copying a new Tab, you just create a new instance of the desired ViewModel, and add it to the ObservableCollection. WPF's DataBinding automatically updates the UI based on the Collection's change notification.
If you want to create additional tab types, just derive from MVVMTabItemViewModel and add your logic and data there. Then, you create a DataTemplate for that new ViewModel and WPF takes care of the rest.
You never, ever, ever manipulate UI elements in procedural code in WPF, unless there's a REAL reason to do so. You don't "uncheck" or "disable" UI Elements because UI elements MUST reflect the STATE of the data which is provided by the ViewModel. So a "Check/Uncheck" state or an "Enabled/Disabled" state is just a bool property in the ViewModel to which the UI binds.
Notice how this completely removes the need for horrendous winforms-like hacks and also removes the need for VisualTreeHelper.ComplicateMyCode() kind of things.
Copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.

Related

Switching user control in WPF only shows the name of ViewModel

I've built WPF app using MVVM design with some simple navigation. I added two Views(User Controls) and dummy ViewModels (empty classess) to the project to switch them through this navigation. Here is my folder structure:
Folder Structure
So when I click i.e. on the first button in navi instead of the view of user control, I got the name of the ViewModel like this:
App screen
(View.ViewModels.NameoftheViewModel)
Here is how I implemented this:
Window.Resources with DataTemplate
Navi buttons for switching user controls
MainWindow.xaml.cs :
public MainWindow()
{
InitializeComponent();
this.DataContext = new NavigationViewModel();
}
NavigationViewModel used for switching :
class NavigationViewModel : INotifyPropertyChanged
{
public ICommand CurrencyCommand { get; set; }
public ICommand GoldCommand { get; set; }
private object selectedViewModel;
public object SelectedViewModel
{
get { return selectedViewModel; }
set { selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public NavigationViewModel()
{
CurrencyCommand = new BaseCommand(OpenCurrency);
GoldCommand = new BaseCommand(OpenGold);
}
public void OpenCurrency(object obj)
{
SelectedViewModel = new CurrencyViewModel();
}
public void OpenGold(object obj)
{
SelectedViewModel = new GoldViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
BaseCommand class simply implements ICommand interface.
XAML for showing user controls :
<ContentControl Margin="45, 50, 0, 0" Content="{Binding SelectedViewModel}"/>
Does anyone knows why is this happening? I did this from the tutorial: Tutorial link and it works all fine for this tutorial case but in my project it does not.
from linked tutorial:
Create a DataTemplate of type EmployeeViewModel and it should contain EmployeeView UserControl inside it. Important thing is you should specify any key to the DataTemplates, since these DataTemplates are going to get queried by their DataType.
you should not specify x:Key for DataTemplates for your view models. DataTemplates can be picked by DataType only if there is no x:Key
<DataTemplate DataType="{x:Type vm:CurrencyViewModel}">
<vs:CurrencyControl/>
</DataTemplate>

MVVM pattern: an intermediate View between Command binding and ViewModel execute

Scenario
Some date are loaded into a program (e.g., evaluation of students in a class where each student is a distinct entity with his/her evaluation data) and a summary of them is shown on a datagrid. The user selects selects some of the students, and performs an analysis on their evaluation. The analysis process requires some parameters, therefore before analysis a window pops-up and lets user to specify his preferred parameters; then the analysis process executes.
Implementation summary
The datagrid is defined as following and binded to a ViewModel:
<DataGrid x:Name="CachedSamplesDG" ItemsSource="{Binding cachedDataSummary}">
<DataGrid.Columns>
<DataGridTextColumn Header="name" Binding="{Binding name}"/>
<DataGridTextColumn Header="score" Binding="{Binding score}"/>
</DataGrid.Columns>
</DataGrid>
The button that starts the process is defined as following:
<Button x:Name="AnalysisBT" Content="Analyze" Command="{Binding AnalyzeCommand}" CommandParameter="{Binding ElementName=CachedSamplesDG, Path=SelectedItems}"/>
The ViewModel is pretty basic and summarized as following:
internal class CachedDataSummaryViewModel
{
public CachedDataSummaryViewModel()
{
_cachedDataSummary = new ObservableCollection<CachedDataSummary>();
AnalyzeCommand = new SamplesAnalyzeCommand(this);
}
private ObservableCollection<CachedDataSummary> _cachedDataSummary;
public ObservableCollection<CachedDataSummary> cachedDataSummary { get { return _cachedDataSummary; } }
public ICommand AnalyzeCommand { get; private set; }
}
And here is the definition of analysis command:
internal class SamplesAnalyzeCommand : ICommand
{
public SamplesAnalyzeCommand(CachedDataSummaryViewModel viewModel)
{
_viewModel = viewModel;
}
private CachedDataSummaryViewModel _viewModel;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
// canExecute logic
}
public void Execute(object parameter)
{
// process mess ...
// Here I need the selected rows of datagird, which "parameter" delegates them.
// I also need some other parameters for analysis which user can set through another view
}
}
An this is a diagram of my current process and what I would like to do next
Question
When the button is clicked
Apply some UI changes on MainWindow
Pop-up ProcessOptionsWindow
Get set parameters from ProcessOptionsWindow
Pass the selected datagrid rows and user specified parameters to SamplesAnalyzeCommand
What would be the best way to achieve this requirement ?
simply use a dialogservice like Good or bad practice for Dialogs in wpf with MVVM?.
then you can do something like this in your ViewModel
var result = this.uiDialogService.ShowDialog("Prozess Options Window", prozessOptionVM);
...
var parameter1 = prozessOptionVM.Parameter1;
You can define another Model and ViewModel for Process Options, and then in the SamplesAnalyzeCommand, display the ProcessOptionsView. When user is done with the ProcessOptionsView, the main ViewModel gets notified (e.g by an event handler) and completes the Process.
Something like this:
internal class SamplesAnalyzeCommand : ICommand {
...
public void Execute(object parameter)
{
this._viewModel.ShowProcessOptions(parameter);
}
}
internal class CachedDataSummaryViewModel {
public string Status {
get {
return this.status;
}
set {
if (!string.Equals(this.status, value)) {
this.status = value;
// Notify property change to UI
}
}
}
...
internal void ShowProcessOptions(object paramter) {
// Model
var processOptions = new ProcessOptionsModel() {
otherInfo = parameter
};
// View-Model
var processOptionsViewModel = new ProcessOptionsViewModel();
processOptionsViewModel.Model = processOptions;
// View
var processOptionsView = new ProcessOptionsView(
processOptionsViewModel
);
// Edit2: Update status
this.Status = "Selecting process options...";
// You can use the event handler or dialog result
processOptionsViewModel.OK += this.PerformProcess;
processOptionsView.ShowDialog();
}
private void PerformProcess(object sender, EventArgs e) {
var processOptionsView = sender as ProcessOptionsView;
var processOptionsModel = processOptionsView.Model;
var processOptions = processOptionsModel.Model;
// Edit2: Update status
this.Status = "Performing process...";
// use processOptions.OtherInfo for initial info
// use processOptions.* for process options info
// and perform the process here
// Edit2: Update status
this.Status = "Process Done.";
}
...
}
class ProcessOptionsModel {
public object OtherInfo {
get;
set;
public int Parameter1 {
get;
set;
}
public IList<ProcessItem> SelectedItems {
get;
set;
}
...
}
class ProcessOptionsViewModel {
public event EventHandler OK;
private SamplesAnalyzeCommand model;
private ICommand okCommand;
public ProcessOptionsViewModel() {
this.okCommand = new OKCommand(this.OnOK);
}
public SamplesAnalyzeCommand Model {
get {
return model;
}
set {
this.model = value;
// Property changed stuff here
}
}
private void OnOK(object parameter) {
if (this.OK != null) {
this.OK = value;
}
}
}
class ProcessOptionsView {
// Interacts with it's view-model and performs OK command if
// user pressed OK or something
}
Hope it helps.
Edit (1):
As blindmeis suggested, you may use some Dialog Service to make the connection between the views.
Edit (2):
Immidiate UI changes after button click can be done in ShowProcessOptions method of the ShowProcessOptions. I don't think you want reflect ui changes of the options window while user works with it, to the main window. UI changes after user closes options window can be done in PerformProcess.
If you want to make an abstraction for options selection (e.g reading from a file) as you mentioned in the comment below, you may define an IOptionsProvider interface, and put ProcessOptionsView and View-Model behind that but still you use the same model.
interface IOptionsProvider {
ProcessOptionsModel GetProcessOptions();
}
class ProcessOptionsView : IOptionsProvider {
public ProcessOptionsModel GetProcessOptions() {
if (this.ShowDialog()) {
return this.ModelView.Model;
}
return null;
}
}
class ProcessOptionsFromFile : IOptionsProvider {
public ProcessOptionsModel GetProcessOptions() {
// Create an instance of ProcessOptionsModel from File
}
}
Note that in this case I removed the OK event since the GetProcessOptions is supposed to block until user closes the main window. If you want a responsive approach in the FromFile case, you may need to work on the async stuff, maybe define GetProcessOptionsAsync instead.
In this case things may get a little bit complicated but I guess it is achievable in this way.

WPF ListView scroll from view model

I have observable collection called (Users) in view model that binded with ListViewControl (lstUsers) in view and what I need is to scroll to current logged in user in List View .
I see in most of examples that used scroll from code behind as following e.g. :
lstUsers.ScrollIntoView(lstUsers[5]);
but what I need is to handle it from view model .
Please advice !
One way of doing this would be to use something like an ICollectionView which has a current item. You can then set IsSynchronizedWithCurrentItem to true to link the current item in the view model to the selected item in the ListView.
Finally handle the event SelectionChanged in the code behind the view to change the scroll position so that it always displays the selected item.
For me the benefit of this method is that the viewmodel is kept unaware of anything about the view which is one of the aims of MVVM. The code behind the view is the perfect place for any code concerning the view only.
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView x:Name="View"
SelectionChanged="Selector_OnSelectionChanged" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Items}"/>
<Button Grid.Row="1" Command="{Binding ChangeSelectionCommand}">Set</Button>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
View.ScrollIntoView(View.SelectedItem);
}
}
public class ViewModel
{
private readonly CollectionViewSource _source = new CollectionViewSource();
public ICollectionView Items
{
get { return _source.View; }
}
public ICommand ChangeSelectionCommand { get; set; }
public ViewModel()
{
SetUp();
ChangeSelectionCommand = new Command(ChangeSelection);
}
private void SetUp()
{
var list = new List<string>();
for (int i = 0; i < 100; i++)
{
list.Add(i.ToString(CultureInfo.InvariantCulture));
}
_source.Source = list;
}
private void ChangeSelection()
{
var random = new Random(DateTime.Now.Millisecond);
var n = random.Next(100);
Items.MoveCurrentToPosition(n);
}
}
public class Command : ICommand
{
private readonly Action _action;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action();
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
_action = action;
}
}
let me share my solution with you
Create your own ListView descendant with dependency property TargetListItem
public class ScrollableListView : ListView
{
/// <summary>
/// Set this property to make ListView scroll to it
/// </summary>
public object TargetListItem
{
get { return (object)GetValue(TargetListItemProperty); }
set { SetValue(TargetListItemProperty, value); }
}
public static readonly DependencyProperty TargetListItemProperty = DependencyProperty.Register(
nameof(TargetListItem), typeof(object), typeof(ScrollableListView), new PropertyMetadata(null, TargetListItemPropertyChangedCallback));
static void TargetListItemPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var owner = (ScrollableListView)d;
owner.ScrollToItem(e.NewValue);
}
public void ScrollToItem(object value)
{
if (value != null && Items != null && Items.Contains(value))
{
ScrollIntoView(value);
}
}
}
create property in ViewModel
object currentListItem;
public object СurrentListItem
{
get => сurrentListItem;
set
{
if (сurrentListItem != value)
{
сurrentListItem = value;
OnPropertyChanged(nameof(СurrentListItem));
}
}
}
bind it
<controls:ScrollableListView ... TargetListItem="{Binding CurrentListItem}"/>
Now you can set CurrentListItem in ViewModel when needed. And the corresponding visual element will become visible in the ListView immediately.
Also maybe you just can use attached property on ListView instead of creating ScrollableListView. But i'm not sure.
Yep, there's always times in MVVM when you need to get at the control. There's various ways of doing this, but here's an easy-ish way of doing it without deriving from the control or messing with routed commands or other such toys what you have in WPF.
In summary:
Create an attached property on your view model.
Set the attached property in XAML to pass the list box back to the view model.
Call .ScrollIntoView on demand.
Note, this is a rough and ready example, make sure your DataContext is set before showing the window.
Code/View Model:
public class ViewModel
{
private ListBox _listBox;
private void ReceiveListBox(ListBox listBox)
{
_listBox = listBox;
}
public static readonly DependencyProperty ListBoxHookProperty = DependencyProperty.RegisterAttached(
"ListBoxHook", typeof (ListBox), typeof (ViewModel), new PropertyMetadata(default(ListBox), ListBoxHookPropertyChangedCallback));
private static void ListBoxHookPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var listBox = (ListBox) dependencyObject;
var viewModel = (ViewModel) listBox.DataContext;
viewModel.ReceiveListBox(listBox);
}
public static void SetListBoxHook(DependencyObject element, ListBox value)
{
element.SetValue(ListBoxHookProperty, value);
}
public static ListBox GetListBoxHook(DependencyObject element)
{
return (ListBox) element.GetValue(ListBoxHookProperty);
}
}
OK, so that will let us get the ListBox passed back to the view; you can do with it as you wish.
Now, just set the property in XAML:
<ListBox wpfApplication1:ViewModel.ListBoxHook="{Binding RelativeSource={RelativeSource Self}}" />
Good to go!

One ViewModel for UserControl and Window or separate ViewModels

I have MainWindow and AddEdit UserControl. Inside MainWindow I render this AddEdit like <Views:AddEditData />, previous this namespace is added to Window element:
xmlns:Views="clr-namespace:MyProject.WPF.Views"
+++++++++++++++ ++++++++++++++++
ListOfData + + DataDetails +
+ + +
DataOne + + Name: txtBox1+
DataTwo + + +
DataThree + + +
+ + Save data +
+++++++++++++++ ++++++++++++++++
When user selects data on left side (DataTwo for example) I want to display it's properties (for simplicity only Name property) inside AddEdit user control (DataDetails panel).
Since this UserControl is stored separately from the MainWindow should I use same MainWindowViewModel and same datacontext or should I create separated ViewModel for AddEdit UserControl?
Hopefully this sounds clear, if not please ask for details.
Part 1. Display the properties of the control in MVVM
As I said in comments:
In MVVM ViewModel should not know about the controls, which are located. In such cases, use the attached behavior or leave the same side logic in View
ViewModel is not directly associated with a View, so just refer to the name of the control would not be right. It would be better to set a property in the Model, and bind it into View via ViewModel, but the property Name does not support Binding (quote from the MSDN):
Data binding a Name is technically possible, but is an extremely uncommon scenario because a data-bound Name cannot serve the main intended purpose of the property: to provide an identifier connection point for code-behind.
so I suggest to use the Tag property or Uid. In my example (give an below), I use Uid property for these purposes.
Part 2. Communication via ViewModels (pattern Mediator)
There are several embodiments of the Mediator pattern, but I like the most the implementation by XAML Guy, it is simple and clear - The Mediator Pattern.
Implementation code
public static class Mediator
{
static IDictionary<string, List<Action<object>>> pl_dict = new Dictionary<string, List<Action<object>>>();
static public void Register(string token, Action<object> callback)
{
if (!pl_dict.ContainsKey(token))
{
var list = new List<Action<object>>();
list.Add(callback);
pl_dict.Add(token, list);
}
else
{
bool found = false;
foreach (var item in pl_dict[token])
if (item.Method.ToString() == callback.Method.ToString())
found = true;
if (!found)
pl_dict[token].Add(callback);
}
}
static public void Unregister(string token, Action<object> callback)
{
if (pl_dict.ContainsKey(token))
{
pl_dict[token].Remove(callback);
}
}
static public void NotifyColleagues(string token, object args)
{
if (pl_dict.ContainsKey(token))
{
foreach (var callback in pl_dict[token])
callback(args);
}
}
}
To demonstrate his work, I created a small example, which consists of two Views, each has its own ViewModel and Model.
The project structure is shown below:
Output
When you click on Button, ListOfData ViewModel communicates via mediator with DataDetails ViewModel, thus:
Mediator.NotifyColleagues("ShowDetails", true);
Mediator.NotifyColleagues("SetSelectedFruit", ListOfDataModel.FruitGreen);
All procedures that interact with the properties must register their ViewModel like this:
private void ShowDetails_Mediator(object args)
{
bool showDetails = (bool)args;
if (showDetails == true)
{
DataDetailsModel.IsVisible = true;
}
else
{
DataDetailsModel.IsVisible = false;
}
}
private void SetSelectedFruit_Mediator(object args)
{
string selectedFruit = (string)args;
DataDetailsModel.SelectedFruit = selectedFruit;
}
public DataDetailsViewModel()
{
DataDetailsModel = new DataDetailsModel();
Mediator.Register("ShowDetails", ShowDetails_Mediator);
Mediator.Register("SetSelectedFruit", SetSelectedFruit_Mediator);
}
In the example I used a DataTemplate instead UserControl. Below are the main part of the project:
MainWindow.xaml
<Window x:Class="CommunicateWithVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:CommunicateWithVM.ViewModels"
Title="MainWindow"
WindowStartupLocation="CenterScreen"
Height="350"
Width="525">
<Grid>
<ContentControl Name="ListOfData"
ContentTemplate="{StaticResource ListOfDataView}">
<ViewModels:ListOfDataViewModel />
</ContentControl>
<ContentControl Name="DataDetails"
ContentTemplate="{StaticResource DataDetailsView}">
<ViewModels:DataDetailsViewModel />
</ContentControl>
</Grid>
</Window>
Models
DataDetailsModel
public class DataDetailsModel : NotificationObject
{
#region SelectedFruit
private string _selectedFruit = "";
public string SelectedFruit
{
get
{
return _selectedFruit;
}
set
{
_selectedFruit = value;
NotifyPropertyChanged("SelectedFruit");
}
}
#endregion
#region IsVisible
private bool _isVisible = false;
public bool IsVisible
{
get
{
return _isVisible;
}
set
{
_isVisible = value;
NotifyPropertyChanged("IsVisible");
}
}
#endregion
}
ListOfDataModel
public class ListOfDataModel : NotificationObject
{
#region FruitGreen
private string _fruitGreen = "Apple";
public string FruitGreen
{
get
{
return _fruitGreen;
}
set
{
_fruitGreen = value;
NotifyPropertyChanged("FruitGreen");
}
}
#endregion
#region FruitYellow
private string _fruitYellow = "Limon";
public string FruitYellow
{
get
{
return _fruitYellow;
}
set
{
_fruitYellow = value;
NotifyPropertyChanged("FruitYellow");
}
}
#endregion
}
ViewModels
DataDetailsViewModel
public class DataDetailsViewModel
{
#region DataDetailsModel
private DataDetailsModel _dataDetailsModel = null;
public DataDetailsModel DataDetailsModel
{
get
{
return _dataDetailsModel;
}
set
{
_dataDetailsModel = value;
}
}
#endregion
#region ShowDetails_Mediator
private void ShowDetails_Mediator(object args)
{
bool showDetails = (bool)args;
if (showDetails == true)
{
DataDetailsModel.IsVisible = true;
}
else
{
DataDetailsModel.IsVisible = false;
}
}
#endregion
#region SetSelectedFruit_Mediator
private void SetSelectedFruit_Mediator(object args)
{
string selectedFruit = (string)args;
DataDetailsModel.SelectedFruit = selectedFruit;
}
#endregion
#region DataDetailsViewModel Constructor
public DataDetailsViewModel()
{
DataDetailsModel = new DataDetailsModel();
Mediator.Register("ShowDetails", ShowDetails_Mediator);
Mediator.Register("SetSelectedFruit", SetSelectedFruit_Mediator);
}
#endregion
}
ListOfDataViewModel
public class ListOfDataViewModel
{
#region ListOfDataModel
private ListOfDataModel _listOfDataModel = null;
public ListOfDataModel ListOfDataModel
{
get
{
return _listOfDataModel;
}
set
{
_listOfDataModel = value;
}
}
#endregion
#region GreenButtonCommand
private ICommand _greenButtonCommand = null;
public ICommand GreenButtonCommand
{
get
{
if (_greenButtonCommand == null)
{
_greenButtonCommand = new RelayCommand(param => this.GreenButton(), null);
}
return _greenButtonCommand;
}
}
private void GreenButton()
{
Mediator.NotifyColleagues("ShowDetails", true);
Mediator.NotifyColleagues("SetSelectedFruit", ListOfDataModel.FruitGreen);
}
#endregion
#region YellowButtonCommand
private ICommand _yellowButtonCommand = null;
public ICommand YellowButtonCommand
{
get
{
if (_yellowButtonCommand == null)
{
_yellowButtonCommand = new RelayCommand(param => this.YellowButton(), null);
}
return _yellowButtonCommand;
}
}
private void YellowButton()
{
Mediator.NotifyColleagues("ShowDetails", true);
Mediator.NotifyColleagues("SetSelectedFruit", ListOfDataModel.FruitYellow);
}
#endregion
#region ListOfDataViewModel Constructor
public ListOfDataViewModel()
{
ListOfDataModel = new ListOfDataModel();
}
#endregion
}
Views
DataDetailsView
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:CommunicateWithVM.ViewModels">
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<DataTemplate x:Key="DataDetailsView" DataType="{x:Type ViewModels:DataDetailsViewModel}">
<StackPanel Width="200"
Background="AliceBlue"
HorizontalAlignment="Right"
Visibility="{Binding Path=DataDetailsModel.IsVisible,
Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Text="Fruit: " />
<TextBlock Text="{Binding Path=DataDetailsModel.SelectedFruit}" />
</StackPanel>
</DataTemplate>
</ResourceDictionary>
ListOfDataView
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:CommunicateWithVM.ViewModels">
<DataTemplate x:Key="ListOfDataView" DataType="{x:Type ViewModels:ListOfDataViewModel}">
<StackPanel Width="200"
Background="Azure"
HorizontalAlignment="Left">
<Button Uid="{Binding Path=ListOfDataModel.FruitGreen}"
Content="GreenButton"
Command="{Binding Path=GreenButtonCommand}" />
<Button Uid="{Binding Path=ListOfDataModel.FruitYellow}"
Content="YellowButton"
Command="{Binding Path=YellowButtonCommand}" />
</StackPanel>
</DataTemplate>
</ResourceDictionary>
This project is available at this link.
As UserControl is maintained separately and not a part of Window content. I would suggest to have separate ViewModel.
Benefits of having separate ViewModel:
Reusability - In future if you want to do some changes in data related to UserControl (may be some logic change), all you have to go to your ViewModel and update it and it will get reflected across all windows. You don't have to worry to go to each Window's view model and update code.
Testability - In case you want to test logic related to your control (data part me talking here not view part), you can write it in isolation. No need to worry about testing of Window view model code.
Loosely Coupled - More than one people can work in isolation. Say one developer have to update some code related to Main window and other have to update some code related to UserControl. With one ViewModel in place, there will be some overlap and they can't work in isolation since there are dependent on other person to do its work before he/she can plug in his/her code in ViewModel.
Also check out here for communication between different ViewModels as you might need that to communicate between Window view model and User Control View model to pass selected data in left window.

INotifyPropertyChanged.PropertyChanged fired, but UI not updated after form loads

Below is an example of my model , ViewModel and xaml binding. The viewmodel implements INotifyPropertChanged. The problem i'm having is...when the wpf form first loads i set ActiveStock and i see both setter and getter being called and the ui is updated to reflect the data correctly.
However, when i later set StockViewModel.ActiveStock, FirePropertyChanged is invoked but i don't see the getter being called, and consequently the UI does not update to reflect the new data. Any ideas what might be happening here?
The second question i have is whether i also need to raise PropertyChanged for the child properties (PriceData and CompanyData) of my model when ViewModel.ActiveStock is changed?
public class Stock
{
public string Ticker { get; set; }
public StockData PriceData { get; set; }
public StockData CompanyData { get; set; }
}
public class StockData
{
...
}
public class StockViewModel:INotifyPropertyChanged
{
private Stock _activeStock;
public Stock ActiveStock
{
get{ return _activeStock;}
set{ _activeStock = value; FirePropertyChanged("ActiveStock");}
}
...
}
XAML:
<UserControl Template="{StaticResource StockTemplate}" DataContext="{Binding ActiveStock}" Tag="{Binding PriceData}" />
<UserControl Template="{StaticResource StockTemplate}" DataContext="{Binding ActiveStock}" Tag="{Binding CompanyData}" />
Edit:
if i remove the DataContext binding for the UserControl and instead set the DataContext for these two controls in code behind when ActiveStock changes, it works fine. why???
The getter is not being called because as far as I can see nothing is "getting" the value, The only properties used are PriceData and CompanyData and these don't use INotifyPropertyChanged
You will have to implement INotifyPropertyChanged on your Stock class for the UI to reflect the changes.
public class Stock : INotifyPropertyChanged
{
private string _ticker;
private StockData _priceData;
private StockData _companyData;
public string Ticker
{
get { return _ticker; }
set { _ticker = value; NotifyPropertyChanged("Ticker"); }
}
public StockData PriceData
{
get { return _priceData; }
set { _priceData = value; NotifyPropertyChanged("PriceData"); }
}
public StockData CompanyData
{
get { return _companyData; }
set { _companyData = value; NotifyPropertyChanged("CompanyData"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
You might want to try to specify the mode property on your datacontext bindings.
DataContext="{Binding ActiveStock, Mode=OneWay}"
I'm not sure that OneTime is the default binding for DataContext, but it would explain so if the above helps.
The second question has been answered by sa_ddam213.
HTH

Categories

Resources