Access parent window's Grid from User Control - c#

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;

Related

WPF binding to a global variable to update UI

Let's say I have two windows and a trayicon context menu. Each of the windows has a togglebutton and the context menu has a checkable menu item. All three controls are designed to display and toggle the status of the same value.
How can I bind, in this case IsChecked, of the three controls to a single global variable that when one of the controls is checked/unchecked that the other controls will update accordingly? Should I just do an invoke or is there an MVVM solution? I'm new to WPF so I'm not sure the best/most correct way to accomplish this.
Lets say you have WindowA, WindowB, ..., WindowN and assume that they all are of different type.
Create a class, lets say CommonState, that encapsulates all common properties, commands, etc. and implements INotifyPropertyChanged
public class CommonState : INotifyPropertyChanged
{
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
if (value != _isChecked)
{
_isChecked = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then declare an interface:
public interface ICommonStateWindow
{
CommonState { get; set; }
}
Make each window implement this interface:
public partial class WindowA : Window, ICommonState
{
public WindowA()
{
InitializeComponent();
}
// This property will be injected, do not re-assign
public CommonState CommonState { get; set; }
}
Inject the common state in each window prior to showing it, for example:
public partial class App : Application
{
private CommonState _state;
protected override void OnStartup(StartupEventArgs e)
{
_state = new CommonState() {IsChecked = true};
var wndA = new WindowA() { CommonState = _state };
var wndB = new WindowB() { CommonState = _state };
wndA.Show();
wndB.Show();
}
}
Remember to keep at least one reference to the created CommonState in some long living object (like App or the main window), so it does not get garbage collected at some point.
In the XAML you should bind using a RelativeSource, so that each new type of window you create can have its own independent ViewModel (DataContext):
<Window x:Class="Example.WindowA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowA" Height="300" Width="300">
<Grid>
<CheckBox IsChecked="{Binding CommonState.IsChecked, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</Grid>
</Window>
The example, I've demonstrated is not the only way and I won't say "the best", but it solves the following problems:
Encapsulates the common (shared) state
Synchronizes the state between different instances (or types) of windows
Allows the CommonState to be extended independently of the window implementation (only the XAML needs to be updated)
Another possible solution is to register a singleton instance of the CommonState into a statically exposed inversion of control container (IoC) and make each concrete window's ViewModel obtain an instance to it. In this way you will avoid the injection step. This would be an overkill for small projects
I anyone is trying to run the above code, remember to remove StartupUri="MainWindow.xaml" from App.xaml
You can add to your codebehind bool IsChecked property and use it for all component you want. And you can change it components' event method to true or false.

Should a child ViewModel be able to change on Parent ViewModel?

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.

Can't update UI from ViewModel in another classes

I've created an application that need a lot of access to UI controls, now what I did firstly is create an interface scalable, in particular I created different controls as UserControl and one class ViewModel that manage all method of this control for update the UI. Actually all working good in the Main thread. In particular the followin scenario working perfect:
MainWindow XAML
xmlns:MyControls="clr-namespace:HeavyAPP"
...
<!-- I use the control in the following way: -->
<Grid>
<MyControls:Scheduler x:Name="Sc"/>
</Grid>
so for example the Scheduler control contains this Data Binding:
<StackPanel Orientation="Horizontal">
<Label x:Name="NextSync" Content="{Binding NextSynchronization, IsAsync=True}" ></Label>
</StackPanel>
ViewModel structure
public class ViewModelClass : INotifyPropertyChanged
{
private CScheduler scheduler;
public ViewModelClass()
{
scheduler = new Scheduler();
}
public string NextSynchronization
{
get
{
return scheduler.GetNextSync();
}
}
}
How you can see in the ViewModel I've an instance of the Scheduler control and a property called NextSyncrhonization as the binding, so this property return a result from the method of the control instance.
For use this in the MainWindow I did the following:
public MainWindow()
{
ViewModelClass viewModel = new ViewModelClass();
DataContext = viewModel;
}
this automatically fill the control property. Now the problem's that I use a BackgroundWorker for perform some task, what I need is use the DataContext of MainWindow from different classes, (not Window, but classes).
For solve this situation I though to do something like this:
MainWindow.AppWindow.Sc.SyncLog.Dispatcher.Invoke(
new Action(() =>
{
ViewModelClass viewModel = new ViewModelClass();
var dataContext = System.Windows.Application.Current.MainWindow.DataContext;
dataContext = viewModel;
viewModel.SynchronizationLog = "this is a test from other thread"}));
now SynchronizationLog is another property that append the text to the Control, just for precision, is this:
private string _text;
public string SynchronizationLog
{
get
{
return _text += _text;
}
set
{
_text = value;
OnPropertyChanged();
}
}
this is the implementation of INotifyPropertyChanged:
`public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}`
this working only in the MainWindow, but in the external classes I can't update the UI, what am I doing wrong?
I reiceve no error, anyway.
Try the following:
extend your ViewModel as follow:
public class ViewModelClass : INotifyPropertyChanged
{
private CScheduler scheduler;
//Add this:
public static ViewModelClass Instance {get; set;} //NEW
public ViewModelClass()
{
scheduler = new Scheduler();
}
public string NextSynchronization
{
get
{
return scheduler.GetNextSync();
}
}
}
This changes your code in the xaml.cs to:
public MainWindow()
{
ViewModelClass.Instance = new ViewModelClass();
DataContext = viewModel.Instance;
}
In your external code you then DONT create a new Instance of the ViewModelClass - instead you use the existing one:
[...].Dispatcher.Invoke(() =>
{
if(ViewModelClass.Instance != null)
{
//Why you need the "var datacontext" in your example here ?
ViewModelClass.Instance.SynchronizationLog = "this is a test from other thread"
}
}));
Basically what you do here is setting a property in your ViewModel from outside of your viewModel. This can be done from everywhere.
What is different to your approach:
We dont create a new Instance of the ViewModel (different bindings in the UI aren't resetted anymore)
We created an Instance so there can always be ONLY ONE viewModel at a time

Open a new Window in MVVM

Lets say I have a MainWindow and a MainViewModel, I'm not using MVVM Light or Prism in this example.
In this MainWindow I want to click a MenuItem or Button to open a NewWindow.xaml not a UserControl.
I know how to use this with UserControl to open a new UserControl in my existing Window in a ContrntControl or a Frame.
<ContentControl Content="{Binding Path=DisplayUserControl,UpdateSourceTrigger=PropertyChanged}" />
Code
public ViewModelBase DisplayUserControl
{
get
{
if (displayUserControl == null)
{
displayUserControl = new ViewModels.UC1iewModel();
}
return displayUserControl;
}
set
{
if (displayUserControl == value)
{
return;
}
else
{
displayUserControl = value;
OnPropertyChanged("DisplayUserControl");
}
}
}
In the ResourceDitionary for MainWindow I have :
<DataTemplate DataType="{x:Type localViewModels:UC1ViewModel}">
<localViews:UC1 />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:UC2ViewModel}">
<localViews:UC2 />
</DataTemplate>
The thing is that I want to open a new Window, not a UserControl. So I use some code like this :
private ICommand openNewWindow;
public ICommand OpenNewWindow
{
get { return openNewWindow; }
}
public void DoOpenNewWindow()
{
View.NewWindowWindow validationWindow = new View.NewWindow();
NewWindowViewModel newWindowViewModel = new NewWindowViewModel();
newWindow.DataContext = ewWindowViewModel;
newWindow.Show();
}
and then a bind OpenNewWindow to a MenuItem or Button.
I know this is not the right way, but what is the right way to do this ?
Thanks!
There are two problems you need to solve with this type of application.
Firstly, you do not want to have the View-Model creating and displaying UI components directly. One of the motivations for using MVVM is to introduce test-ability in to your View-Model, and having this class pop up new windows makes this class harder to test.
The second problem you need to solve is how to resolve the dependencies in your application, or in this instance – how to you “hook up” the View-Model to the corresponding View? A maintainable solution to this latter problem is given by the use of a DI container. A very good reference to this subject is given by Mark Seemann’s Dependency Injection in .NET. He actually also discusses how to solve the first problem too!
To solve the former problem, you need to introduce a layer of indirection to your code, to make the View-Model not dependent on a concrete implementation of creating a new window. A very simple example is given in the code below:
public class ViewModel
{
private readonly IWindowFactory m_windowFactory;
private ICommand m_openNewWindow;
public ViewModel(IWindowFactory windowFactory)
{
m_windowFactory = windowFactory;
/**
* Would need to assign value to m_openNewWindow here, and associate the DoOpenWindow method
* to the execution of the command.
* */
m_openNewWindow = null;
}
public void DoOpenNewWindow()
{
m_windowFactory.CreateNewWindow();
}
public ICommand OpenNewWindow { get { return m_openNewWindow; } }
}
public interface IWindowFactory
{
void CreateNewWindow();
}
public class ProductionWindowFactory: IWindowFactory
{
#region Implementation of INewWindowFactory
public void CreateNewWindow()
{
NewWindow window = new NewWindow
{
DataContext = new NewWindowViewModel()
};
window.Show();
}
#endregion
}
Note that you take an implementation of IWindowFactory in the constructor of your View-Model, and it is to this object that the creation of the new window is delegated to. This allows you to substitute the production implementation for a different one during testing.

INotifyPropertyChanged binding not updating as expected

So here is the problem I'm beating my head against: I have a custom user control that exposes two dependency properties that are bound to my ViewModel. In my ViewModel I have an instance of a class that holds multiple properties that express values that relate to the user control as well as to items that control manipulates. Here's a bit of sample code to explain it visually so here is a simple sample of my control, it's a Slider that is combined with a checkbox that allows the user to lock the slider.
<custom:SliderControl IsLocked="{Binding Path=CustomClass.IsLocked, Mode=TwoWay}" SliderValue="{Binding Path=CustomClass.Value, Mode=TwoWay}" />
IsLocked and SliderValue are dependency properties that effectively manipulate the checkbox and slider that are contained in the custom control. All of the control functions work as intended, except for the bindings to the class I've defined. If I create individual properties, as in one int property and one bool property the bindings work as intended. However I have five sliders, and each slider in my actual code has five properties that tie in to them. I'm trying to eliminate code duplication by creating a class to hold these properties in a reusable object shrinking my 25 properties down to 5 class instances.
My CustomClass inherits ObservableObject and has a bool property and int property named IsLocked and SliderValue respectively. For more visual aids here is what it looks like:
public class CustomClass : ObservableObject
{
public const string SliderValuePropertyName = "SliderValue";
private int _sliderValue= 0;
public int SliderValue
{
get
{
return _sliderValue;
}
set
{
if (_sliderValue== value)
{
return;
}
_sliderValue= value;
RaisePropertyChanged(SliderValuePropertyName );
}
}
public const string IsCheckedPropertyName = "IsChecked";
private bool _isChecked = false;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
if (_isChecked == value)
{
return;
}
_isChecked = value;
RaisePropertyChanged(IsCheckedPropertyName);
}
}
The ViewModel property is very similar and looks like this, an new instance of the class is created when the ViewModel loads:
public const string SliderOnePropertyName = "SliderOne";
private CustomClass _sliderOne;
public CustomClass SliderOne
{
get
{
return _sliderOne;
}
set
{
if (_sliderOne== value)
{
return;
}
_sliderOne= value;
RaisePropertyChanged(SliderOnePropertyName );
}
}
Why won't the updating of the dependency property that is bound to the property in the class update properly? Is it because you can't properly update the class instance property by itself and instead have to update the entire class instance whenever changes occur? Or do I need to further customize the setter in this ViewModel property? As it sits now changing the slider value or checkbox never hits the bound property at all and nothing errors out when debugging.
EDIT: I've also surrounded the control in a Border and set the Border UIElement's DataContext to that of the class and then subsequently applied the more simple path binding to the underlying custom control. This however did not have any effect on my problem.
I'm a homegrown programmer so I often miss things when putting code together and I'm guessing this is the case here, unless what I'm trying just won't work.
Any help would be greatly appreciated.
EDIT: So I've been toying around with using a custom event that will let me know when the specific property of the custom control changes and then having that event wired up in my ViewModel to update the existing class. This works but still creates code duplication as now I have to have 10 events, 2 events per control, one to check for when the value of the slider changes and the other to detect when the checkbox IsChecked value changes. This code duplication exists since you can't route multiple command parameters (like a simple string identifier for which slider is being manipulated as well as the value you want to use in the code). This limitation means I can't just use 2 events that differentiate between which control is undergoing changes within the defined method as exposing the physical control to the ViewModel breaks the MVVM pattern. Using a class as the datacontext for the user control made it so I didn't care what control was being manipulated as they each had their own class instance. Using events this unravels the MVVM pattern as now I need to know which of the five controls is being manipulated by the user.
It can't be this hard to use a class in property bindings. I have to be missing something remedial.
here is a full example:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel
{
public SliderValues slv { get; private set; }
public ViewModel()
{
slv = new SliderValues();
}
}
public class SliderValues : INotifyPropertyChanged
{
bool _isLocked = false;
public bool IsLocked
{
get { return _isLocked; }
set
{
_isLocked = value;
OnPropertyChanged("IsLocked");
}
}
int _theValue = 5;
public int TheValue
{
get { return _theValue; }
set
{
_theValue = value;
OnPropertyChanged("TheValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Now the xaml:
<UserControl x:Class="TestBindings.MainPage"
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"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Slider Height="23" HorizontalAlignment="Left" Margin="114,138,0,0" Name="slider1" VerticalAlignment="Top" Width="100"
DataContext="{Binding slv}" Value="{Binding TheValue, Mode=TwoWay}"/>
</Grid>
</UserControl>
May be there is just a syntactical error. Try this
{Binding Path=CustomClass.IsLocked, Mode=TwoWay}
Try this...<custom:SliderControl DataContext="{Binding CustomClass}" IsLocked="{Binding IsLocked, Mode=TwoWay}" SliderValue="{Binding Value, Mode=TwoWay}" />

Categories