Should a child ViewModel be able to change on Parent ViewModel? - c#

I have a situation and I'm not sure if I'm doing it correct.
I have a ApplicationViewModel that is my "shell" for my whole application.
And within that viewmodel I have other child-ViewModels.
public ApplicationModelView()
{
ModelViewPages.Add(new GameViewModel());
ModelViewPages.Add(new EditGameViewModel());
//Set Current HomePage
CurrentPage = ModelViewPages[0];
}
#endregion
#region Properties
public BaseModelView CurrentPage
{
get { return _currentPage; }
set { _currentPage = value; OnPropertyChanged(); }
}
public List<BaseModelView> ModelViewPages
{
get
{
if (_modelViewPages == null){_modelViewPages = new List<BaseModelView>();}
return _modelViewPages;
}
}
#endregion
In my GameViewModel I display a list of objects from my model GamesModel that contains title,description etc.
When I click on an item in this list it becomes selected and then I want to change my View to EditGameViewModel with a button but I'm not sure if how to do it.
How can I get my child-ViewModel to change content in my parent-ViewModel?
Or should the child even do that?
EDIT
How I want it to function
I want when I select an item and click on button that I change from the view GameViewModel to EditGameViewModel with the data that I have selected from the list.
public void EditGame(object param)
{
//MessageBox.Show("From EditGame Function"); HERE I WANT TO CHANGE THE VIEWMODEL ON MY APPLICATIONVIEWMODEL
}
public bool CanEditGame(object param)
{
return SelectedGame != null;
}

I can offer something that works, but could be questionable. It all really depends on how you plan for your application to function.
First, similar to your MainViewModel, you want something like this:
public class MainViewModel : ObservableObject //ObservableObject being a property change notification parent
{
//Current view will always be here
public BaseViewModel ViewModel { get; set; }
public MainViewModel()
{
//By default we will say this is out startup view
Navigate<RedViewModel>(new RedViewModel(this));
}
public void Navigate<T>(BaseViewModel viewModel) where T : BaseViewModel
{
ViewModel = viewModel as T;
Console.WriteLine(ViewModel.GetType());
OnPropertyChanged("ViewModel");
}
}
Now, since we are navigating this way, every child view needs to derive from BaseViewModel.
public class BaseViewModel : ObservableObject
{
private MainViewModel _mainVM;
public BaseViewModel(MainViewModel mainVM)
{
_mainVM = mainVM;
}
protected void Navigate<T>() where T : BaseViewModel
{
//Create new instance of generic type(i.e. Type of view model passed)
T newVM = (T)Activator.CreateInstance(typeof(T), _mainVM);
//Change MainViewModels ViewModel to the new instance
_mainVM.Navigate<T>(newVM);
}
}
Now we just really need to see how we are going to have child views delegate this call of change. So we will have a BlueView.
public class BlueViewModel : BaseViewModel
{
//Relay command to call 'ToRed' function
public ICommand ChangeToRed
{
get { return new RelayCommand(ToRed); }
}
//Requires MainViewModel for BaseViewModel
public BlueViewModel(MainViewModel main) : base(main)
{
}
//Calling BaseViewModel function. Passed BaseViewModel Type
public void ToRed(object param)
{
Navigate<RedViewModel>();
}
}
And a RedView:
public class RedViewModel : BaseViewModel
{
//Relay command to call 'ToBlue' function
public ICommand ChangeToBlue
{
get { return new RelayCommand(ToBlue); }
}
//Requires MainViewModel for BaseViewModel
public RedViewModel(MainViewModel main) : base(main)
{
}
//Calling BaseViewModel function. Passed BaseViewModel Type
public void ToBlue(object param)
{
Navigate<BlueViewModel>();
}
}
Our MainWindow.xaml could look like this:
<Window.DataContext>
<viewmodels:MainViewModel/>
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding ViewModel}" />
</Grid>
Doing this all children will be able to call to their parent that they would like a change. The BaseViewModel holds this parent for all children who derive, so they can pass it back and forth during navigation like a baton.
Again, Navigation all really depends on how you are using, building and planning for your application. It's not always a one size fits all way.

Related

C# Wpf mvvm keep multiple ViewModels with model sychronized

I've data architecture issues. My goal should be to have bidirectional data communication
between ViewModels and the Model classes. I've one window with different usercontrols. Each usercontrol
has it's own data, but some properties are shared between these.
For each ViewModel I implemented two functions for synchronize the model and the viewmodel.
The model should kept updated, so I implemented in the PropertyChanged event the method call SyncModel.
This is so far not so nice, because when I call the constructor the method call chain is:
constructor -> SyncViewModel -> Property setter -> PropertyChanged -> SyncModel
Here is some sample code to understand my problem better:
public class SampleModel
{
public string Material { get; set; }
public double Weight { get; set; }
public double Length { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public object SharedProperty { get; set; }
}
public class SampleViewModelA : AbstractViewModel
{
public string Material
{
get
{
return _Material;
}
set
{
if (value != _Material)
{
_Material = value;
OnPropertyChanged(nameof(Material));
}
}
}
public double Weight
{
get
{
return _Weight;
}
set
{
if (value != _Weight)
{
_Weight = value;
OnPropertyChanged(nameof(Weight));
}
}
}
public object SharedProperty
{
get
{
return _SharedProperty;
}
set
{
if (value != _SharedProperty)
{
_SharedProperty = value;
OnPropertyChanged(nameof(SharedProperty));
}
}
}
public SampleViewModelA(SampleModel Instance) : base(Instance) { }
public override void SyncModel()
{
//If I wouldn't check here, it would loop:
//constructor -> SyncViewModel -> Property setter -> PropertyChanged -> SyncModel
if (Instance.Material == Material &&
Instance.Weight == Weight &&
Instance.SharedProperty == SharedProperty)
return;
Instance.Material = Material;
Instance.Weight = Weight;
Instance.SharedProperty = SharedProperty;
}
public override void SyncViewModel()
{
Material = Instance.Material;
Weight = Instance.Weight;
SharedProperty = Instance.SharedProperty;
}
private string _Material;
private double _Weight;
private object _SharedProperty;
}
public class SampleViewModelB : AbstractViewModel
{
//Same like SampleViewModelA with Properties Length, Width, Height AND SharedProperty
}
public abstract class AbstractViewModel : INotifyPropertyChanged
{
//All ViewModels hold the same Instance of the Model
public SampleModel Instance { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public AbstractViewModel(SampleModel Instance)
{
this.Instance = Instance;
SyncViewModel();
}
protected virtual void OnPropertyChanged(string PropertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
SyncModel();
}
public abstract void SyncModel();
public abstract void SyncViewModel();
}
The real problem is, that the SharedProperty need to be updated between the SampleViewModelA and the SampleViewModelB. First I thought the Observer Pattern could help me, but the SharedProperties are to various to make it work with generic interfaces. Then I thought a datacontroller with change events could help me like this
public class SampleDataController
{
public SampleModel Instance { get; set; }
public delegate void SynchronizeDelegate();
public event SynchronizeDelegate SynchronizeEvent;
public void SetSharedProperty(object NewValue)
{
if (Instance.SharedProperty != NewValue)
{
Instance.SharedProperty = NewValue;
SynchronizeEvent?.Invoke();
}
}
}
If it would do it like this my AbstractViewModel would only communicate with the controller instead of the instance. The SyncModel function would call methods like SetSharedProperty instead of directly access.
The MainViewModel code could look like this.
public class SampleMainViewModel
{
public SampleViewModelA ViewModelA { get; set; }
public SampleViewModelB ViewModelB { get; set; }
public SampleDataController Controller { get; set; }
public SampleMainViewModel()
{
ViewModelA = new SampleViewModelA(Controller);
ViewModelB = new SampleViewModelB(Controller);
Controller.SynchronizeEvent += ViewModelA.SyncViewModel;
Controller.SynchronizeEvent += ViewModelB.SyncViewModel;
}
}
This would cause the problem, that the source for the SynchronizeEvent call is also
subscribed to the event itself. This wouldn't cause a infinity loop because I check if
values are equal to the new state, but it seems very ugly to me. There must be a better
way than this.
In my project I have 8 ViewModels and multiple model classes where I need to sychronize the data with
different shared properties.
I'm thankful for any help and hope the problems are so far understandable.
You already use a SampleMainViewModel which is composed of the other view model classes SampleViewModelA and SampleViewModelB.
Now all you have to do is to move all the properties that are shared between the view models/views (like the SharedProperty, but also Material and Weight) to the composed SampleMainViewModel or to a shared class in general. This way all your controls can bind to the same data source.
Also the communication between Model --> View Model should only take place via events: the Model can notify the View Model by exposing e.g., a DataChanged event. There is no real bi-directional communication/dependency between Model and View Model. That's the main characteristic of MVVM: the uni-directional dependency of the participating components - realized by implementing events, commands and especially by utilizing data binding.
The follwoing example shows how you bind your controls to shared properties and to unshared properties (those that are attributes of the specialized view model classes).
MainWindow.xaml
<Window>
<Window.DataContext>
<SampleMainViewModel />
</Window.DataContext>
<StackPanel>
<UserControlA Material="{Binding Material}"
SharedProperty="{Binding SharedProperty}"
UnsharedPropertyA="{Binding ViewModelA.UnsharedPropertyA}" />
<UserControlB Material="{Binding Material}"
SharedProperty="{Binding SharedProperty}"
UnsharedPropertyB="{Binding ViewModelB.UnsharedPropertyB}" />
</StackPanel>
</Window>
SampleMainViewModel.cs
public class SampleMainViewModel : INotifyPropertyChanged
{
public SampleViewModelA ViewModelA { get; }
public SampleViewModelB ViewModelB { get; }
/* Properties must raise INotifyPropertyChanged.PropertyChanged */
public string Material { get; set; }
public double Weight { get; set; }
public object SharedProperty { get; set; }
// Example initialization
public SampleMainViewModel(SomeModelClass someModelClass)
{
this.ViewModelA = new SampleViewModelA();
this.ViewModelB = new SampleViewModelB();
this.Material = someModelClass.Material;
this.Weight = someModelClass.Weight;
this.SharedProperty = someModelClass.SharedProperty;
someModelClass.DataChanged += UpdateData_OnDataChanged;
}
private void UpdateData_OnDataChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Material = someModelClass.Material;
this.Weight = someModelClass.Weight;
this.SharedProperty = someModelClass.SharedProperty;
}
}
SampleViewModelA.cs
public class SampleViewModelA : INotifyPropertyChanged
{
public object UnsharedPropertyA { get; set; }
}
SampleViewModelB.cs
public class SampleViewModelB : INotifyPropertyChanged
{
public object UnsharedPropertyB { get; set; }
}
"Your suggestion cause the disadvantage, that my separated ViewModels
are no longer encapsulated. And if I would move all my code with
shared properties to the MainViewModel, the class would end up very
large"
To address your comment: if you insist in having a view model per each control including duplicating properties etc., then you must take a different approach.
Also moving the shared/duplicate code out of your view models does not break encapsualtion - assuming that those classes do not contain duplicated code only.
But note that a view model per each control is not recommended. You have a view/page - an aggregation of multiple controls - which has a defined data context. All controls in this view share the same data context - usually, as views are structured context related.
That's why the FrameworkElement.DataContext is inherited by default.
Having a view model for each control makes things overly complicated and leads to a lot of duplicated code - and not only duplicate properties like in your example. You will find yourself duplicating logic too. And talking about testability, if you duplicate logic you will duplicate unit tests too.
This is because you are dealing with the same data and the same model classes.
You usually extract duplicate code to a separate class that is the referenced by the types that depend on this duplicate code. Refactoring your view model classes with this "no duplicate code" policy in mind would end up moving the "shared" properties to a separate class. Since we are talking about the data context of the same view, this separate class would be the view model class that is assigned to the DataContext of the page. I'm trying to say that your approach is doing the exact opposite: you duplicate code (and call it encapsulation). If the class ends up being very large because it contains a lot of properties then you may review your UI design - maybe you should split your big page into more pages with more concise content. This may will improve UX too.
Generally, there is nothing wrong with having a view model with some more properties. If your view model class contains lots of logic too, you can extract this logic to separate classes.
You can still use the pattern of the previous example, which is to listen to data changed events of the Model.
Either you implement a very general event like the above DataChanged event or several more specialized events like a MaterialChanged event. Also make sure to inject the same model instances into each view model.
The following example shows how you can have multiple different view model classes that expose the same data, where all these view model classes update themselves by observing their model classes:
MainWindow.xaml
<Window>
<Window.DataContext>
<SampleMainViewModel />
</Window.DataContext>
<StackPanel>
<UserControlA DataContext="{Binding ViewModelA}"
Material="{Binding Material}"
Weight="{Binding Weight}"
SharedProperty="{Binding SharedPropertyA}" />
<UserControlB DataContext="{Binding ViewModelB}"
Material="{Binding Material}"
Weight="{Binding Weight}"
SharedProperty="{Binding SharedPropertyB}" />
</StackPanel>
</Window>
SampleMainViewModel.cs
public class SampleMainViewModel : INotifyPropertyChanged
{
public SampleViewModelA ViewModelA { get; }
public SampleViewModelB ViewModelB { get; }
// Example initialization
public SampleMainViewModel()
{
var sharedModelClass = new SomeModelClass();
this.ViewModelA = new SampleViewModelA(sharedModelClass);
this.ViewModelB = new SampleViewModelB(sharedModelClass);
}
}
SampleViewModelA.cs
public class SampleViewModelA : INotifyPropertyChanged
{
/* Shared properties */
public string Material { get; set; }
public double Weight { get; set; }
public object SharedProperty { get; set; }
private SomeModelClass SomeModelClass { get; }
// Example initialization
public SampleViewModelA(SomeModelClass sharedModelClass)
{
this.SomeModelClass = sharedModelClass;
this.Material = this.SomeModelClass.Material;
this.Weight = this.SomeModelClass.Weight;
this.SharedProperty = this.SomeModelClass.SharedProperty;
// Listen to model changes
this.SomeModelClass.DataChanged += UpdateData_OnDataChanged;
this.SomeModelClass.MaterialChanged += OnModelMaterialChanged;
}
// Example command handler to send dat back to the model.
// This will trigger the model to raise corresponding data chnaged events
// to notify listening view model classes that new data is available.
private void ExecuteSaveDataCommand()
=> this.SomeModelClass.SaveData(this.Material, this.Weight);
private void OnModelMaterialChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Material = someModelClass.Material;
}
private void UpdateData_OnDataChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Weight = someModelClass.Weight;
this.SharedProperty = someModelClass.SharedProperty;
}
}
SampleViewModelB.cs
public class SampleViewModelB : INotifyPropertyChanged
{
/* Shared properties */
public string Material { get; set; }
public double Weight { get; set; }
public object SharedProperty { get; set; }
private SomeModelClass SomeModelClass { get; }
// Example initialization
public SampleViewModelB(SomeModelClass sharedModelClass)
{
this.SomeModelClass = sharedModelClass;
this.Material = this.SomeModelClass.Material;
this.Weight = this.SomeModelClass.Weight;
this.SharedProperty = this.SomeModelClass.SharedProperty;
// Listen to model changes
this.SomeModelClass.DataChanged += UpdateData_OnDataChanged;
this.SomeModelClass.MaterialChanged += OnModelMaterialChanged;
}
// Example command handler to send dat back to the model.
// This will trigger the model to raise corresponding data chnaged events
// to notify listening view model classes that new data is available.
private void ExecuteSaveDataCommand()
=> this.SomeModelClass.SaveData(this.Material, this.Weight);
private void OnModelMaterialChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Material = someModelClass.Material;
}
private void UpdateData_OnDataChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Weight = someModelClass.Weight;
this.SharedProperty = someModelClass.SharedProperty;
}
}
A variation of the first solution (which aimes to eliminate duplicate code) is to refactor your binding source, that exposes the shared properties, by extracting those properties to new classes according to their responsibilities.
For example, you can have your MainViewModel expose a MaterialViewModel class that encapsulates material related properties and logic. This way MaterialViewModel can be mnade available globally.
Given that you follow the one-data-context-class per view principle, you can limit the scope of the shared properties to specific pages by having only their specific view model classes expose the same MaterialViewModel instance:
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<StackPanel>
<MaterialControl DataContext="{Binding MaterialViewModel}"
Material="{Binding Material}"
Weight="{Binding Weight}" />
<UserControlB ... />
</StackPanel>
</Window>
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
// Since defined in 'MainViewModel' the properties of 'MaterialViewModel'
// are globally shared accross pages
public MaterialViewModel MaterialViewModel { get; }
/* View model classes per page */
public ViewModelPageA PageViewModelA { get; }
// If 'ViewModelPageB' would expose a 'MaterialViewModel',
// you can limit the visibility of 'MaterialViewModel' to the 'ViewModelPageB' DataContext exclusively
public ViewModelPageB PageViewModelB { get; }
// Example initialization
public SampleMainViewModel()
{
var sharedModelClass = new SomeModelClass();
this.MaterialViewModel = new MaterialViewModel(sharedModelClass);
this.ViewModelPageA = new ViewModelPageA(sharedModelClass);
// Introduce the MaterialViewModel to a page specific class
// to make the properties of 'MaterialViewModel' to be shared inside the page only
this.ViewModelPageB = new ViewModelPageB(sharedModelClass);
}
}
MaterialViewModel.cs
public class MaterialViewModel : INotifyPropertyChanged
{
public string Material { get; set; }
public double Weight { get; set; }
private SomeModelClass SomeModelClass { get; }
// Example initialization
public MaterialViewModel(SomeModelClass sharedModelClass)
{
this.SomeModelClass = sharedModelClass;
this.Material = this.SomeModelClass.Material;
this.Weight = this.SomeModelClass.Weight;
// Listen to model changes
this.SomeModelClass.MaterialDataChanged += OnModelMaterialChanged;
}
// Example command handler to send dat back to the model.
// This will also trigger the model to raise corresponding data chnaged events
// to notify listening view model classes that new data is available.
// It can make more sense to define such a command in the owning class,
// like SampleMainViewModel in this case.
private void ExecuteSaveDataCommand()
=> this.SomeModelClass.SaveData(this.Material, this.Weight);
private void UpdateData_MaterialChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Material = someModelClass.Material;
this.Weight = someModelClass.Weight;
}
}

How to inherit ViewModel class to UserControl?

Suppose that I've a ViewModel class with different property such as:
//MainVm will inherit ViewModel that contains IPropertyChanged implementation
public class MainVM : ViewModel
{
publi bool Foo { get; set; }
}
I instatiated this class on the main controller in this way:
MainController mc;
public MaiWindow()
{
mc = new MainController();
DataContext = mc;
InitializeComponent();
}
the MainController have this implementation:
public class MainController : MainVM
{
some methods
}
so each time I need to access to a property of MainController on each UserControls I need to do: Application.Current.MainWindow.mc.Foo
and this is't elegant at all.
Is possible access to the property of specific ViewModel without call the code line above?
Thanks.
UPDATE
For add more details and clarification to this question: so in my UserControl I need to access to the Foo property that is part of MainVM. I'm spoke about the UserControl xaml code (not the controller of the UserControl), this is an example:
public partial class ControlName : UserControl
{
public ControlName()
{
InitializeComponent();
}
public btnAdd_Click(object sender, RoutedEventArgs e)
{
//Suppose that I need to access to the property Foo of MainVM here
//I need to access to MainWindow instance like this:
Application.Current.MainWindow.mc.Foo
//isn't possible instead access to the property directly inherit the class, like:
MainVm.Foo
}
}
In order to get the configuration App-Wide, you could use the
ConfigurationManager.AppSettings["Whatever"];
These settings are located in the App.Config in the following form
<appSettings>
<add key="Whatever" value="Hello" />
</apSettings>
But as I undertand, you have a ViewModel that let Users change the settings, in this case you should go for:
Properties.Settings.Default.myColor = Color.AliceBlue;
You ViewModel could expose this property as:
public Color MyColor
{
get {
return Properties.Settings.Default.myColor;
}
set {
Properties.Settings.Default.myColor = value; RaisePropertyChanged();
}
}
public void Persist()
{
Properties.Settings.Default.Save();
// Raise whatever needed !
}
From other ViewModels you can access these setting as well:
Properties.Settings.Default.myColor
Have a look here https://learn.microsoft.com/en-us/dotnet/framework/winforms/advanced/using-application-settings-and-user-settings and here https://learn.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-create-application-settings

Access parent window's Grid from User Control

In my MainWindow VM i open the Views from my UserControls like this.
Usercontrol1 is the name of the View made in Xaml.
In my ViewModel of my MainWindow:
private static Grid _myMainGrid;
public static Grid MyMainGrid
{
get { return _myMainGrid; }
set { _myMainGrid = value; }
}
private void OpenUserControl(UserControl myUS)
{
if (MyMainGrid.Children.Count > 0)
{
MyMainGrid.Children.RemoveAt(0);
}
Grid.SetColumn(myUS, 1);
Grid.SetRow(myUS, 0);
MyMainGrid.Children.Add(myUS);
}
private void FindGrid(object obj)
{
var myGrd = obj as Grid;
if (myGrd != null)
{
MyMainGrid = myGrd;
}
}
The command binding to the Button executes this.
private void ExecuteCMD_OpenUserControl1(object obj)
{
FindGrid(obj);
Usercontrol1 _ucItem = new Usercontrol1();
OpenUserControl(_ucItem);
}
Now i want to open Usercontrol2 replacing Usercontrol1 in MyMainGrid from my MainWindow by pressing a button in Usercontrol1. So i have to access the parent Window.
Tried using this methode but can't get it to work in my case.
Let's say you have two children; it's trivial to generalize this to any number of children. Fortunately you've already got viewmodels and views, so we're most of the way there. It's just a matter of wiring it all together in a way that works well with WPF.
Here's a set of skeletal viewmodels. Main, and two children. MainViewModel creates its two child instances. ChildOneViewModel has a Next button command, bound to a Button in ChildOneView.xaml.
When the user clicks that button, we want to switch the active view to child two. Rather than have dependencies going in all directions, ChildOneViewModel is ignorant of what "next" really means; its parent, MainViewModel, is in charge of that. Everybody knows about his children; you've found that in programming, making a class know about its siblings or its parent creates problems.
So all ChildOneViewModel does is expose an event so MainViewModel knows when the button is clicked, and can take any action it likes when that happens. This is cool because what if we could be going to one of two different "next" pages, depending on what the user did in ChildOne? If we move that responsibility to the parent, everything becomes simpler. Easier to write, easier to reuse in a different context.
#region MainViewModel Class
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
ChildOne = new ChildOneViewModel();
ChildTwo = new ChildTwoViewModel();
ActiveChild = ChildOne;
ChildOne.NextButtonClicked += (s, e) => ActiveChild = ChildTwo;
}
#region ActiveChild Property
private INotifyPropertyChanged _activeChild = default(INotifyPropertyChanged);
public INotifyPropertyChanged ActiveChild
{
get { return _activeChild; }
set
{
if (value != _activeChild)
{
_activeChild = value;
OnPropertyChanged();
}
}
}
#endregion ActiveChild Property
public ChildOneViewModel ChildOne { get; private set; }
public ChildTwoViewModel ChildTwo { get; private set; }
}
#endregion MainViewModel Class
#region ChildOneViewModel Class
public class ChildOneViewModel : ViewModelBase
{
public event EventHandler NextButtonClicked;
// You already know how to implement a command, so I'll skip that.
protected void NextButtonCommandMethod()
{
NextButtonClicked?.Invoke(this, EventArgs.Empty);
}
}
#endregion ChildOneViewModel Class
#region ChildTwoViewModel Class
public class ChildTwoViewModel : ViewModelBase
{
}
#endregion ChildTwoViewModel Class
And here's the XAML that translates all of that into actual UI at runtime:
<Window.Resources>
<!--
These are "implicit datatemplates": They have no x:Key, so they'll be
automatically used to display any content of the specified types.
-->
<DataTemplate DataType="{x:Type local:ChildOneViewModel}">
<local:ChildOneView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ChildTwoViewModel}">
<local:ChildTwoView />
</DataTemplate>
</Window.Resources>
<Grid>
<!-- other stuff -->
<UserControl
Content="{Binding ActiveChild}"
/>
<!-- other stuff -->
You don't need OpenUserControl(), FindGrid(), any of that stuff.
I don't fully understand the structure of your application and there are most probably better ways of doing whatever you are trying to do, but you could get a reference to any open window in your application using the Application.Current.Windows property if that's your main issue:
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
Inside a loaded UserControl, you should also be able to get a reference to the parent window of this UserControl like this:
var mainWindow = Window.GetWindow(this) as MainWindow;

Interact with SelectemItem of Treeview

I have implemented the SelectedItem of a treeview according to the following article: Data binding to SelectedItem in a WPF Treeview
The SelectedItem works perfect and it truly reflects the item selected.
Now I want to interact with the selected item, but I'm not sure how. I can create an instance of the BindableSelectedItemBehavior class, but this is not the instance containing the data I'm looking for. How to access the class instance holding the SelectedItem of the treeview?
This is a highlight of my code:
namespace QuickSlide_2._0
{
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
...
}
public partial class Window1 : Window
{
.....
private void New_subject_Click(object sender, RoutedEventArgs e)
{
// here the code to read the instance of the class BindableSelectedItemBehavior and interact
// with the selectedItem when I click on the button
}
}
}
Maybe I'm looking totally in the wrong direction. Your help would be highly appreciated!
You want to use MVVM so that you can bind the dependency property of your behavior and then you can access the value of the SelectedItem that you made.
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
//Dependency property called SelectedTreeViewItem register it
}
public partial class Window1 : Window
{
public Window1()
{
DataContext = new WindowViewModel();
}
private void New_subject_Click(object sender, RoutedEventArgs e)
{
// here the code to read the instance of the class BindableSelectedItemBehavior and interact
// with the selectedItem when I click on the button
var windowViewModel = DataContext as WindowViewModel;
var selectedItem = windowViewModel.SelectedItem;
}
}
public class WindowViewModel()
{
public object SelectedItem { get; set; } // You want to change object to the type you are expecting
}
and on your View
<TreeView>
<TreeView.Behaviors>
<BindableSelectedItemBehavior SelectedTreeViewItem="{Binding SelectedTreeViewItem,Mode=TwoWay}">
</...>
</...>

When accessing window from view model it is always null

What i want to do is to create property into the model ComboBoxItemChange.cs of type ILoginView that is the interface which LoginWindow.xaml.cs is deriving. Using this property i want to grant access to the elements inside LoginWindow. I red that this is correct way to do it using MVVM pattern.
My problem is that property is always null.
LoginWindow.xaml.cs
public partial class LoginWindow : Window, ILoginView
{
public LoginWindow()
{
InitializeComponent();
this.DataContext = new ComboBoxItemChange();
(this.DataContext as ComboBoxItemChange).LoginWindow = this as ILoginView;
}
public void ChangeInputFieldsByRole(string role)
{
MessageBox.Show(role);
}
}
ComboBoxItemChange.cs
public class ComboBoxItemChange : INotifyPropertyChanged
{
public ILoginView LoginWindow { get; set; }
private void ChangeloginWindowInputFields(string value)
{
if (LoginWindow == null)
return;
LoginWindow.ChangeInputFieldsByRole(value);
}
}
ILoginView.cs
public interface ILoginView
{
void ChangeInputFieldsByRole(string role);
}
As stated in comment:
There are two different instances you are creating:
One in code behind where you set ILoginView to window itself
Second in Grid resources where you haven't set ILoginView.
Remove the instance you declared in XAML and let the bindings resolved from the instance you created in code behind. (DataContext will automatically be inherited for child controls).

Categories