Open a new Window in MVVM - c#

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.

Related

combobox in usercontrol not fill - WPF & mvvm

Good day, I'm new to WPF with MVVM, the problem in general is when filling a combox, but I'll give you some context:
I have a user control that contains different controls, including a combobox which I try to fill from the modelview but I am not successful. The user control is invoked from a main window in a few words the flow would be something like this
mainview.xaml->usercontrol.xaml->usercontrolmodelview.cs
this is where I define the source of the combobox that is inside the user control (usercontro.xaml):
<ComboBox x:Name="cbConcept" Width="150" ItemsSource="{Binding Path=Concepts}" DisplayMemberPath="textConcept" />
in this it is in usercontrolmodelview.cs linked to my user control, I define a list called Concepts that I fill it through a service (the service if it returns information and fills the list).
private IEnumerable<Concept> _concepts;
public IEnumerable<Concept> Concepts { get => _concepts; set { _concepts = value; } }
public usercontrolmodelview()
{
AddItemCommand = new ViewModelCommand(ExecuteAddCommand, ValidateAddCommand);
_api = new Api();
_memCache = new MemCache();
_ = LoadCatalogs();
}
private async Task LoadCatalogs()
{
_concepts = _memCache.GetCache<IEnumerable<Concept>>(KeysCache.CompanyCache);
if (_concepts == null)
{
_companys = await _api.GetConcept();
_memCache.SaveCache(_concepts, KeysCache.CompanyCache);
}
}
and this way I invoke the user control in my main window (xaml):
<ContentControl Content="{Binding currentChildView}"
Grid.Row="2"
Margin="20"/>
this the code in the main principal (cs):
public ICommand cmdControl { get; }
private ViewModelBase _curretnChildView;
public ViewModelBase currentChildView
{
get { return _curretnChildView; }
set
{
_curretnChildView = value;
OnPropertyChanged(nameof(currentChildView));
}
}
public MainViewModel()
{
cmdControl = new ViewModelCommand(ExecuteShowAddUserControl);
}
private void ExecuteShowAddUserControl(object obj)
{
currentChildView = new usercontrolmodelview();
}
xaml code where the command that shows the user control is linked:
<RadioButton Style="{StaticResource menuButton}"
Tag="{StaticResource colorClosed}"
Command="{Binding cmdControl}">
</RadioButton>
as extra data the user control if it is displayed in the main window.
I have tried to change the type of source by datacontext and even so the combobox is not filled
Your usercontrolmodelview should implement INotifyPropertyChanged to tell your view when a property changed and the view needs to refresh that proprety. This is espacially important when dealing with async operations.
A sample implementation could look like this:
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
I also like to create a ViewModelBase class which implements this interface and make all my viewmodels inherit from it, but this is personal preference.
In the Setter of your public IEnumerable Concepts, you call NotifyPropertyChanged(nameof(Concepts));
Please note, that if you collection of concepts might change later, you should use a ObservableCollaction and call the PropertyChangedEvent on the CollectionChanged Event handler.
Hope this helps
~BrainyXS

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.

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;

Action on ComboBox selection changed

I'm using MVVM to bind a ComboBox to a ViewModel, and I have few question about heavy actions and selection change.
I want to trigger some actions when the selected item is changed, my initial approach was to put the logic in the setter of the field to which the selected item is binded.
So my first question is, is this good practice or there is a better approach?
Those actions may be very expensive in time and resources (need to retrieve data through a web service) and I don't want the UI to freeze, so lately I've started to send a message from the set which is received in the view's code-behind and that call a ViewModel command asynchronously.
Am I just wasting time or does this make any sense?
The problem is that when I'm debugging the UI sometimes freeze anyway (it doesn't happened on release). Reading here and there I've come to know that it may be debugger related, can anyone confirm this behavior on VS 2015?
Additional information
As requested I provide some examples. This is my first approach:
(XAML)
<ComboBox SelectedItem="{Binding SelectedField}"/>
(ViewModel)
public class ViewModel
{
private MyObject _selectedField = null;
public MyObject SelectedField
{
get
{
return _selectedField;
}
set
{
if(_selectedField != value)
{
// Expensive action
_selectedField = value;
RaisePropertyChanged(() => SelectedField);
}
}
}
}
The expensive action make some web service calls and may take long, is this design good or is there a better way to achieve this?
My second approach is through messages, as shown in this example:
(ViewModel)
public class ViewModel
{
private MyObject _selectedField = null;
public MyObject SelectedField
{
get
{
return _selectedField;
}
set
{
if(_selectedField != value)
{
Messenger.Default.Send(new DoStuffMessage());
_selectedField = value;
RaisePropertyChanged(() => SelectedField);
}
}
}
private RelayCommand _doStuffCommand = null;
public ICommand DoStuffCommand
{
get
{
if (_doStuffCommand == null)
_doStuffCommand = new RelayCommand(async () => await DoStuff());
return _doStuffCommand;
}
}
private async Task DoStuff()
{
// Expensive action
}
}
(Code-behind)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Messenger.Default.Register<DoStuffMessage>(this, DoStuffMessage_Handler);
}
private void DoStuffMessage_Handler(DoStuffMessage msg)
{
(DataContext as ViewModel).DoStuffCommand.Execute(null);
}
}
Is this approach better or is just bad and useless?
For MVVM, I prefer to use RelayCommands to bind an EventTrigger in XAML to an ICommand in the viewmodel. I feel this creates the best separation of code and is clearer than adding a lot of logic to my setters, where it might be overlooked during troubleshooting. Here is an overview of the process: https://msdn.microsoft.com/en-us/magazine/dn237302.aspx
This is to wire up a button and pass in a parameter, so obviously you would need to modify it for your use case, but it will show the basic technique. In XAML:
<Button Content="Click Me">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Custom:EventToCommand Command="{Binding MyCommand}" CommandParameter="foo"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
In your VM:
public static ICommand MyCommand { get; set; } // declare an ICommand - bind to this!
public MainViewModel(IDataService dataService)
{
// associate your ICommand with a method. If you don't use a parameter, you don't need the lambda expression here.
MyCommand = new RelayCommand<string>((paramater) => MyCommandMethod(parameter));
}
public void MyCommandMethod(string parameter)
{
Debug.WriteLine("This is the code I want to run in my VM. The parameter is " + parameter);
}
I use the [free] MVVMLight toolkit for my applications, which was written by the guy who wrote the article that I linked to, but a lot of this is baked into .Net also. Using Expression Blend can make it easier to wire this stuff up when you are designing.
You can do whatever you like in setter as long as it is async.
private string _test;
public string Test
{
get { return _test; }
set
{
Task.Run(() =>
{
//do stuff
});
_test = value;
}
}
If you don't want to place logic in setter, because for example the Single Responsibility principle is violated, you should use interactions to catch the SelectionChange event and call a command in VM which should call an async method.
Here you have a sample that uses interactions : cute link
That's it!

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

Categories