View inside other View (WPF/MVVM) - c#

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}" ... />

Related

Learning MVVM. Calling Methods

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

Passing data between two usercontrols / Views

Using MVVM
I am trying to pass data entered in a control (a textbox in the attached code) in one view (view1) and use that data in the second view (view2). At the moment, by declaring all my views in the App.xaml file, I can bind the textblock in view2 with the information entered in the textbox in view1 and see it displayed in the said textblock. But I want to use the information entered in view2's view model as well but dont know how to access it there to use the information.
Can somebody tell me how to go about doing this? Thanks!
App.xaml [declaration of resources]
<Application.Resources>
<vws:DefaultVM x:Key="DefaultVMApp"></vws:DefaultVM>
<vws:View1 x:Key="View1App"></vws:View1>
<vws:View2 x:Key="View2App"></vws:View2>
<vm:AppVM x:Key="AppVMApp"></vm:AppVM>
<vm:View1VM x:Key="View1VMApp"></vm:View1VM>
<vm:View2VM x:Key="View2VMApp"></vm:View2VM>
</Application.Resources>
View1.xaml
<UserControl.DataContext>
<StaticResource ResourceKey="View1VMApp"></StaticResource>
</UserControl.DataContext>
<Grid Background="Aqua">
<StackPanel Margin="100">
<TextBox x:Name="firstNameTextBoxView1" Text="{Binding View1InfoClass.FirstName, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Button Command="{Binding Source={StaticResource AppVMApp}, Path=View2ButtonCommand}" Content="Go to view2" Height="20" Width="70" />
</StackPanel>
</Grid>
View2.xaml
<UserControl.DataContext>
<StaticResource ResourceKey="View2VMApp"></StaticResource>
</UserControl.DataContext>
<Grid Background="Beige">
<StackPanel Margin="100">
<TextBlock x:Name="View1TextBlock" Text="{Binding Source={StaticResource View1VMApp}, Path=View1InfoClass.FirstName}" ></TextBlock>
</StackPanel>
</Grid>
AppVM
public class AppVM : ObservableObject
{
//Create a property that controls current view
private static object _currentView = new DefaultVM();
public object CurrentView
{
get { return _currentView; }
private set
{
OnPropertyChanged(ref _currentView, value);
}
}
private string _textboxText;
public string TextboxText
{
get { return _textboxText; }
set
{
OnPropertyChanged(ref _textboxText, value);
}
}
public AppVM()
{
View1ButtonCommand = new RelayCommand(ShowView1, AlwaysTrueCommand);
View2ButtonCommand = new RelayCommand(ShowView2, AlwaysTrueCommand);
DefaultCommand = new RelayCommand(ShowDefault, AlwaysTrueCommand);
}
//Instantiate the relaycommands, we will need to instantiate relaycommand objects for every command we need to perform.
//This means that we will need to do this for preses of all buttons
public RelayCommand View1ButtonCommand { get; private set; }
public RelayCommand View2ButtonCommand { get; private set; }
public RelayCommand DefaultCommand { get; private set; }
public void ShowDefault(object dummy)
{
CurrentView = new DefaultVM();
}
public void ShowView1(object dummy)
{
CurrentView = new View1();
}
public void ShowView2(object dummy)
{
CurrentView = new View2();
}
public bool AlwaysTrueCommand(object dummy)
{
return true;
}
}
The fundamental problem in your code is that you have dedicated a pre-defined view model object to each of the user controls. This is really bad. A user control's data context must be left alone, for the client code (e.g. your main window) to determine, and to use for binding to specific properties that the user control exposes.
Unfortunately, there's not enough context in your question to provide a clear, complete answer. But to fix your issue, you need to do things differently:
First and foremost, "decouple" the view models you are using for your user control from the user controls themselves. Do this by adding dependency properties to each user control, and then letting the main view where the user controls are used decide what to bind to each of those dependency properties. Do not allow the user controls themselves to set their own data contexts.
Having done that, you may find that you can just use the same view model for the two user controls as for the main view. I.e. you'll set the main view's data context to the single view model, the user controls will inherit that data context, and you'll bind, for example, the TextboxText property to the appropriate declared dependency property in each user control. This way, that single property will represent state for both user controls at the same time.
One hopes that will be enough to get you back on track. If not, consider searching Stack Overflow for other questions related to view models and their relationships to user controls. For example, these questions:
Issue with DependencyProperty binding
XAML binding not working on dependency property?
WPF DataBinding with MVVM and User Controls
Other questions which don't address your scenario exactly, but which should give you some ideas for alternative ways to structure your view model(s):
MVVM : Share data between ViewModels
Sharing non control related data between WPF ViewModel and UserControl
Sharing data between different ViewModels
Sharing state between ViewModels

Prism WPF: Loading a User Control on Startup

I've seen some examples on changing the user control in only one window using Prism for WPF and it looks like this:
Bootstrapper.cs
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType(typeof(object), typeof(ViewA), "ViewA");
Container.RegisterType(typeof(object), typeof(ViewB), "ViewB");
}
MainWindowViewModel.cs
public class MainWindowViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
public DelegateCommand<string> NavigateCommand;
public MainWindowViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<string>(ExecuteNavigateCommand);
}
private void ExecuteNavigateCommand(string uri)
{
_regionManager.RequestNavigate("ContentRegion", uri);
}
}
MainWindow.xaml
<Button Grid.Row="1" Grid.Column="1" Command="{Binding NavigateCommand}" CommandParameter="ViewA" FontSize="16" Content="View A" Margin="4"/>
<Button Grid.Row="1" Grid.Column="2" Command="{Binding NavigateCommand}" CommandParameter="ViewB" FontSize="16" Content="View B" Margin="4"/>
You can click it and the views will change. But when you start it, there is no user control loaded, only the main window. My question is How can you load a user control to the MainWindow on start of the application?
You have different options here. First of all, register the view for the region so that it is automatically discovered and displayed. That works if you don't want to navigate to that view afterwards.
_regionManager.RegisterViewWithRegion("MyRegion", typeof(ViewA));
Alternatively, you can navigate to the view when the application is done with starting. That is, from the end of Bootstrapper.InitializeModules. If the bootstrapper doesn't know of the view or you want to do other things at this moment, too, you can also publish an event like ModulesInitialized and let the module defining your view subscribe to that event.
// in the assembly defining the interfaces shared between your modules
public class ModulesInitialized : PubSubEvent {}
// in the bootstrapper.InitializeModules
Container.Resolve<IEventAggregator>().GetEvent<ModulesInitialized>().Publish();
// in the module defining viewA
_eventAggregator.GetEvent<ModulesInitialized>().Subscribe( () => _regionManager.Requestnavigate( "MyRegion", "ViewA" ), true );

Communication between ViewModel and UserControl's view

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.

ICommand Dependency Property

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.

Categories

Resources