I working on WPF MVVM project. I'm struggling with communication between viewmodel of my MainWindow and view of usercontrol, placed inside MainWindow.
So I have:
UserControl
MainWindow
MainWindowViewModel
My UserControl is very simple:
<Grid MouseDown="UIElement_OnMouseDown">
<Rectangle Fill="BlueViolet" />
</Grid>
with code-behind (just rise an event when rectangle is clicked, and pass coordinates):
public partial class FooUserControl : UserControl
{
public FooUserControl()
{
InitializeComponent();
}
public event EventHandler<BarEventArgs> BarClick;
private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
{
double x = e.GetPosition(this).X;
double y = e.GetPosition(this).Y;
string value_to_pass = "[" + x + "," + y + "]";
BarEventArgs bar = new BarEventArgs() { Bar = 2, Foo = value_to_pass };
BarClick?.Invoke(sender, bar);
}
}
My MainWindow doesn't have code-behind. Just xaml. As you can see I pass click event via Command to MainWindowViewModel:
<Window.DataContext>
<viewModels:MainWindowViewModel />
</Window.DataContext>
<Grid>
<local:FooUserControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="BarClick">
<cmd:EventToCommand Command="{Binding ClickedCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</local:FooUserControl>
</Grid>
and finally my MainWindowViewModel has just this command:
public class MainWindowViewModel : ObservableObject
{
public ICommand ClickedCommand => new RelayCommand<BarEventArgs>(o => Clicked(o.Foo));
private void Clicked(string a)
{
Debug.WriteLine("Clicked " + a);
}
}
So, communication from UserControl's view to MainWindow's viewmodel, via command, works great. But, how can I communicate in opposite way? From MainWindowViewModel to UserControl's view?
Your ViewModels should not access your Views directly. They should not care about Views at all. All they do, is, provide properties to make data available. Views can now bind to these properties.
So, all communication from the ViewModel to the View works through Bindings only. When the ViewModel has to tell the View something, it provides a property. Then it's up to the View to bind to that property and do something with it - whatever this might be.
MVVM says,view should talk only to its viewmodel and viewmodels can talk to other viewmodels only(and model).
What you need is a Mediator.
Source : http://dotnetpattern.com/mvvm-light-messenger/
With this you don't have to create event in your usercontrol.you can communicate to any viewmodel that is instantiated.
You can use mvvm-light,which provides an implementation of Mediator pattern(Messenger).it also provides other tools that will help you build MVVM application.
here is a tutorial to MVVMLight Messenger.
With binding you can update the view appropriately.
thus viewmodels talk to each other and views are updated by corresponding view. this way you wont be violating any MVVM principle.
Related
I have this UserControl called ControlButtonsView
<Grid>
<Button Style="{StaticResource MinimizeButton}" Command="{Binding MinimizeAppCommand}" Height="40" Width="120" VerticalAlignment="Top" HorizontalAlignment="Right"/>
<Button Content="X" Style="{StaticResource ExitButton}" Command="{Binding ExitAppCommand}" Height="40" Width="60" VerticalAlignment="Top" HorizontalAlignment="Right"/>
</Grid>
and ControlButtonsViewModel
class ControlButtonsViewModel
{
private MainWindow _mainWindow;
public ICommand MinimizeAppCommand { get; set; }
public ICommand ExitAppCommand { get; set; }
public ControlButtonsViewModel(MainWindow mainWindow)
{
_mainWindow = mainWindow;
MinimizeAppCommand = new BaseICommand(MinimizeApp);
ExitAppCommand = new BaseICommand(ExitApp);
}
public void MinimizeApp(object obj)
{
_mainWindow.WindowState = System.Windows.WindowState.Minimized;
}
public void ExitApp(object obj)
{
_mainWindow.Close();
}
}
In my MainWindow.xaml.cs
this.DataContext = new AppManagerViewModel();
AppManagerViewModel controls the switching between Views
What I want is to be able to use this ControlButtonsView with its ControlButtonsViewModel in multiple other Views, this view is a UserControl with a minimize and a maximize buttons and I want to use them in multiple Views, in LogInView, MenuView etc.
If there is an easier way to do this please tell me) Thank you.
Window logic does not belong to the view model. View model does not care about UI. You must always implement the view model pretending like there is no UI, only a model.
Therefore having a reference of MainWindow in you view model will lead to a tight coupling of the application to the view/UI.
The goal of MVVM is to remove this tight coupling. Obviously, due to the tight coupling you have introduced, you are currently not implementing the MVVM pattern (you are implementing it wrong).
For example, you won't be able to test the view model without creating a view.
Injecting the view as constructor dependency makes it even worse.
Because the commands execute UI logic (close, minimize), they have to be moved to a control - to the view component from a MVVM point of view.
To make those commands available throughout your view or globally relative to the actual visual tree, you should implement those commands as routed commands e.g. on your MainWindow, which you want to control via commanding.
Since routed commands are static, they can be referenced by every other control. Because they are routed, they can be used everywhere in the same visual tree that the command target (the MainWindow) belongs to.
Internally the command, once executed, will raise a routed event which will traverse the visual tree until it finds a handler.
Commanding Overview
In your case, MainWindow will register the Execute and CanExecute handler to close or minimize itself.
The following example implements only the logic to close the Window.
You can follow the pattern to provide additional logic e.g. to maximize the Window:
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static readonly RoutedUICommand CloseWindowRoutedCommand = new RoutedUICommand(
"Closes the application.",
nameof(MainWindow.CloseWindowRoutedCommand),
typeof(MainWindow));
public MainWindow()
{
InitializeComponent();
this.CommandBindings.Add(
new CommandBinding(MainWindow.CloseWindowRoutedCommand,
ExecuteCloseWindow,
CanExecuteCloseWindow));
}
private void CanExecuteCloseWindow(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = true;
private void ExecuteCloseWindow(object sender, ExecutedRoutedEventArgs e) => Close();
}
ControlButtonsView.xaml
<Grid>
<-- ICommand traverse visual tree until handler(s) is found -->
<Button Content="X" Command="{x:static MainWindow.CloseWindowRoutedCommand}" />
</Grid>
In AppManagerViewModel, add a property of ControlButtonsViewModel.
public ControlButtonsViewModel ControlButtonsViewModel {get; set;}
In the constructor of AppManagerViewModel, add
ControlButtonsViewModel = new ControlButtonsViewModel();
In Xaml of AppManagerView,
<ControlButtonsView DataContext="{Binding ControlButtonsViewModel}" ... />
I'm currently learning how MVVM works and gettings a bit confused.
What I Have Now: I've got a MainWindow.xaml and have made a button that adds in UserControl1.xaml adding it to a ContentControl, which all works great. I've got a folder named ViewModels with a class named SettingsViewModel.cs and another folder named Views with a UserControl named SettingsView.xaml
What I'm trying to figure out: In the User Control I'll have things like buttons, checkboxes, and some other stuff, I want to be able to have a button press in the MainWindow to call a method where I can do stuff like changing the visibility of items among other things. How I go about calling this method from the MainWindow and where to put the method [SettingsViewModels.cs or SettingsView.xaml].
I'm still very new to programming so I'm probability leaving out a bunch of info, so ask me any question.
I have accually got this to work the other way around; calling a method in MainWindow from a UserControl like this...
//this is in the UserControl
private void Button1_Click(object sender, RoutedEventArgs e)
{
MainWindow callMethod = (MainWindow)Application.Current.MainWindow;
callMethod.MyMethod1();
}
//this is in the MainWindow
pubic void MyMethod1()
{
//whatevery i want here
}
There are a couple of things to consider. In MVVM, View communicate to ViewModel through bindings and ViewModel communicate to the View through events typical from INotifyPropertyChanged and ICollectionChanged. Buttons should be binded to a property of type ICommand. The ViewModel should not know about WPF control stuff like Visibility etc.
To change visibility you use an IValueConverter called BooleanToVisiblityConverter.
Without quite understanding what you are asking, here is some pseudo code of how I would do it.
The structure of your files doesn't matter, but dividing them into Views and ViewModels is a good idea.
Disclaimer: This code will not run, shows only the concept. I left Visual Studio on my other computer.
ViewModel:
public class MainWindowViewModel
{
public ICommand OpenCommand { get; }
public object Child { get; private set; }
public MainWindowViewModel()
{
OpenCommand = new RelayCommand(Open);
}
private void DoOpen()
{
Child = new ChildViewModel();
}
}
public class ChildViewModel
{
public bool ShowSomething { get; }
}
public class Program
{
private void SomeStartupLogic()
{
var window = new MainWindow();
windows.DataContext = new MainWindowViewModel(); // or use an IoC container
window.Show();
}
}
View
<Window class="MainWindow">
<Window.Resources>
<DataTemplate DataType="{x:Type ChildViewModel}">
<ChildView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding Child}"/>
<Button Command="{Binding OpenCommand}"/>
</Grid>
</Window>
<UserControl class="ChildView">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConvert"/>
</UserControl.Resources>
<Grid>
<TextBlock Text="Something" Visibility="{Binding ShowSomething, Converter={StaticResource BooleanToVisibilityConvert}/>
</Grid>
</UserControl>
Links
MVVM
Commands
PropertyChanged
first attempt at MVVM and WPF (steep learning curve).
In my ViewModel I want to run the following code to add a "layoutDocument" which is an AvalonDock layout into my Mainform UI.
ViewModel class:
LayoutDocument layoutDocument = new LayoutDocument { Title = "Plan Layout" };
Window mainWindow = Application.Current.Windows.OfType<Window>().Where(x => x.Name == "MainWindow").FirstOrDefault();
if (mainWindow != null)
{
mainWindow.mainPanel.Children.Add(layoutDocument);
}
The above code gives me the following error:
"'Window' does not contain definition for 'mainPanel' and no extension method for 'mainPanel'".
Note in my XAML below that "LayoutDocumentPane" does contain a name "mainPanel".
I have tried adding the above code directly into my MainForm View Class (excluding the Application.Current.Windows.OfType and If statement bit) and just including the:
mainPanel.Children.Add(layoutDocument);
And it works fine (a new layout is created in my MainForm when I click the button).
However, as I want to stickto MVVM this is not a suitable solution.
How can I add "layoutDocument" to MainWindow from ViewModel? Thanks in advance.
An extract of my XAML looks like this:
<Window x:Class="LiveExplorer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LiveExplorer"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:vm="clr-namespace:WpfApp1.ViewModel">
<Grid> etc etc etc here---
<xcad:LayoutDocumentPaneGroup>
<xcad:LayoutDocumentPane x:Name="mainPanel">
<xcad:LayoutDocument ContentId="document1" Title="Document 1" >
<Button Content="Document 1 Content" HorizontalAlignment="Center" VerticalAlignment="Center"
Command="{Binding NewPlanCommand, Source={StaticResource viewModel}}"
/>
</xcad:LayoutDocument>
<xcad:LayoutDocument ContentId="document2" Title="Document 2">
<TextBox Text="Document 2 Content" AcceptsReturn="True"/>
</xcad:LayoutDocument>
</xcad:LayoutDocumentPane>
</xcad:LayoutDocumentPaneGroup >
EDIT:
Whilst the accepted answer does not answer the question in terms of MMVM, it does correct the coding error.
What you've tried to implement does not follow the MVVM pattern. You need to take care of 3 things to get started:
ViewModels
Initialize the ViewModel binded to the window
Binding ViewModel to the UI in XAML
ViewModels:
Create a viewmodel that will be binded to your MainWindow and create an observable collection inside that MainWindowViewModel that contains a list of object that will contain data that can be used in the UI:
public ObservableCollection<LayoutDocumentViewModel> LayoutDocument {get;set;}
Make sure that both the MainWindowViewModel and the LayoutDocumentViewModel inherits from INotifyPropertyChanged(Implement Property Change Notification) or if you use MVVMLight (or similar) from ViewModelBase.
The LayoutDocumentViewModel is just a ViewModel that will be used to store information about your layout document and that can be binded to the UI.
public LayoutDocumentViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName]
string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Name");
}
}
}
I would strongly recommend that you use MVVMLight (or similar) or put the INotifyPropertyChange code into a base class i.e. ViewModelBase for example.
For simplicity sake in this example, I'm initializing the observable collection and creating a couple of document layouts objects directly in the MainWindowViewModel but you'll need to research this further and find out where it is appropriate for you to initialize and/or create these.
public MainPageViewModel()
{
DocumentLayouts = new ObservableCollection();
DocumentLayouts.Add(new DocumentLayout {Name="Layout1"});
DocumentLayouts.Add(new DocumentLayout {Name="Layout2"});
}
The above takes care of creating your MainWindowViewModel and layout documents.
Initializing MainViewModel (and binded to the MainWindow.xaml). Note this is a quick and dirty way to get you started and you should really look into IoC containers.
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
Finally, bind your ViewModel & UI
XAML:
<Grid>
<ItemsControl ItemsSource="{Binding LayoutDocuments}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Note: Just replace the Label by your LayoutDocument control and bind it to the relevant element properties you have declared in LayoutDocumentViewModel.
Hope this helps get you started.
This is not related to MVVM but to be able access the mainPanel you need to cast the returned Window to a MainWindow:
MainWindow mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
if (mainWindow != null)
{
mainWindow.mainPanel.Children.Add(layoutDocument);
}
A view model shouldn't access any window directly though. This breaks the MVVM pattern.
I have an UserControl with a button inside. This button needs to add some items to a Grid that's inside said UC. I'm aware I can do this with a Click event.
The issue here is I am using MVVM and altering data outside their corresponding ViewModel would break the format (So to say).
Is there a way to create an ICommand Dependency Property so I can bind said DP to the button and have the functionality of adding the item to the Grid in my ViewModel? (I already have the List in both my UC and my ViewModel and they are working as expected)
Thank you.
Found a way to solve it in the way I was trying to. Leaving the answer here so people may use it:
1) In your User Control's code-behind, create a Dependency Property. I choose ICommand, since in my ViewModel I set it as a DelegateCommmand:
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(UserControl));
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
2) In your UserControl's XAML code, bind this Dependency Property (In this case, a button):
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
<Button Command="{Binding Command}" />
</Grid>
3) Next, on your ViewModel, declare a Command property and configure accordingly:
public ICommand ViewModelCommand { get; set; }
public ViewModelConstructor()
{
ViewModelCommand = new DelegateCommand(ViewModelCommandExecute);
}
private void ViewModelCommandExecute()
{
// Do something
}
4) Finally, on your View where the UserControl is nested, we declare the binding:
<UserControls:UserControl Command={Binding ViewModelCommand}/>
This way, the binding will take place and you can bind Commands from the buttons of any User Control to your ViewModels without breaking MVVM.
The basic way is to create an Object (ie MyCommand) which implements ICommand, and nest it inside your ViewModel. Inside MyCommand you have no access to your ViewModel. You can workaround it (ie pass a reference to the ViewModel in MyCommand constructor) but at the end it's too much code (for simple stuff like this). I think almost nobody really do this.
Most use a DelegateCommand which resolve (most of) the above issues.
Last but not least, just use event handlers.
If you code them simply like this:
void Grid_MouseMove(object sender, MouseEventArgs e)
{ viewModel.SaveMousePosition(e.GetPosition()); }
you are not breaking any MVVM rule.
And you can't handle the above event with Commands.
There is no Command for MouseMove (there is none for most events), and you can't pass event parameters in a Command.
You can handle every event using Interaction.Triggers like this
But you still miss the capability to handle event parameters (and add ugly XAML).
To me, until WPF will support databinding in event handlers, like
Grid MouseMove="{Binding SaveMousePosition(e)}"
code behind is still the most effective way to handle events.
I faced similar problem and this question/answers helped me the most; so I will post my solution here in case somebody else will google it later. Made with mvvm light.
I had a custom winforms control as a Model and a WPF control as a View. So, xaml of View (I have an usercontrol for my View, no app.xaml):
<UserControl.Resources>
<ResourceDictionary>
<viewModel:ViewModelLocator x:Key="Locator" />
</ResourceDictionary>
</UserControl.Resources>
<UserControl.DataContext>
<Binding Path = "Main" Source="{StaticResource Locator}"></Binding>
</UserControl.DataContext>
<Grid>
<Button Command="{Binding Zoom, ElementName=Wrapper}"></Button>
<viewModel:ProfileWrapper x:Name="Wrapper" >
</viewModel:ProfileWrapper>
</Grid>
Click of a Button is routed to a RelayCommand Zoom in ProfileWrapper (which is where my Model implemented)
Then the xaml of ProfileWrapper is straghtforward:
<Grid>
<WindowsFormsHost>
<local:ManualControl x:Name="abc" ></local:ManualControl>
</WindowsFormsHost>
</Grid>
And the codebehind of ProfileWrapper :
public partial class ProfileWrapper : UserControl
{
public ProfileWrapper()
{
InitializeComponent();
test = abc;
Command = new RelayCommand(() => test.bZoomIn());
}
public ManualControl test;
public RelayCommand Zoom { get; set; }
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Zoom",
typeof(ICommand),
typeof(ProfileWrapper));
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
}
My MainViewModel class is empty and all fuctionality goes to ProfileWrapper class, which might be bad, but at least it works.
I know it's a generic title, but my question is specific. I think it will boil down to a question of practice. So, I have the following code:
public partial class MainWindow : Window
{
InitializeComponent();
MyViewModel viewModel = new MyViewModel();
this.myGrid.DataContext = viewModel;
}
public class MyViewModel
{
public ICommand SomeCommandProperty { get { return this.someCommandProperty; }}
}
public class ComponentCollection : Panel
{
public ComponentCollection()
{
for (int i = 0; i < n; i++)
{
this.Children.Add(new Component());
}
}
}
public class Component : UIElement
{
public Component()
{
this.InputBindings.Add(new MouseBinding(SomeCommandProperty, new MouseGesture(MouseAction.LeftClick)));
}
}
I could easily aggregate the ViewModel that owns SomeCommandProperty into the Component class, but I'm currently waiving that option assuming there is another way.
Component is a child of ComponentCollection which is child of a Grid which DataContext is MyViewModel. ComponentCollection as the name suggests contains a collection of Components.
<Grid Name="myGrid">
<someNamespace:ComponentCollection x:Name="componentCollection"/>
</Grid>
It's the same scenario as the XAML below, but with TextBlock. I guess I'm trying to replicate what's being done in the XAML below programatically. Again, Component's top most ancestor's DataContext is set to ViewModel.
<Grid Name="myGrid">
<TextBlock Text="SomeText">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding SomeCommandProperty}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
</Grid>
Update 1
Basically, I have a custom control which inherit from a Panel which children are a collection of Component. It's not a hack, like I've mentioned, I could directly have access to SomeCommandProperty If I aggregate the ViewModel into Component. Doing so, however, feels icky. That is, having direct access to ViewModel from a Model.
I guess the question I'm asking is. Given the situation that Component's parent UIElement's DataContext is set to MyViewModel, is it possible to access SomeCommandProperty without Component owning a reference to the MyViewModel that owns SomeCommandProperty? Programatically, that is.
Using ItemsControl doesn't change the fact that I still need to bind SomeCommandProperty to each Items.
Update 2
See code above.
Update 3
Apparently, there isn't a mechanism I know of that will set the binding on the Command property of an InputBinding.
For example if my Component class were to Inherit from ButtonBase instead of UIElement, I would have the Command property to which I could easily set the binding programatically using FrameWorkElement's SetBinding. Unfortunately, I can't do this with InputBinding's Command property.
public class Component : ButtonBase
{
public Component()
{
System.Windows.Data.Binding binding = new System.Windows.Data.Binding
{
RelativeSource = new System.Windows.Data.RelativeSource(System.Windows.Data.RelativeSourceMode.FindAncestor, typeof(ComponentCollection), 1 ),
Path = new PropertyPath("DataContext.SomeCommandProperty")
};
// I can do this.
this.SetBinding(this.CommandProperty, binding);
// But I want to do something like below. Note: It's a pseudo code.
MouseBinding mouseBinding = new MouseBinding();
mouseBinding.SetBinding(mouseBinding.CommandProperty, binding);
this.InputBindings.Add(mouseBinding);
}
}
Update 4
BindingOperations.SetBinding can be used on Objects that don't have direct access to SetBinding.
Solution
MouseBinding mouseBinding = new MouseBinding();
BindingOperations.SetBinding(mouseBinding, MouseBinding.CommandProperty, binding);
this.InputBindings.Add(mouseBinding);
Use an ItemsControl for this. Don't try to hack something together yourself when there is a built-in class that already does this.
You can also access the ViewModel from a parent UI element in the Visual Tree by using a RelativeSource binding:
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="SomeText">
<TextBlock.InputBindings>
<!-- See how I'm using RelativeSource to get a hold of the DataContext of the parent ItemsControl -->
<MouseBinding Command="{Binding DataContext.SomeCommandProperty,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>