I have inherited a Sketchflow-prototyped WPF application. I am in the process of adding functionality to the UI.
As it currently is, navigation from screen to screen is defined in XAML as an event as follows:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<pi:NavigateToScreenAction TargetScreen="WpfPrototype1Screens.Screen_1_2"/>
</i:EventTrigger>
</i:Interaction.Triggers>
This works, but the problem is that it doesn't allow me to validate user input before navigating to the new screen.
What I would like to know, is how can I programmatically navigate to the new screen from within the button's click event handler in C#?
For instance:
private void Button1_Click_1(object sender, RoutedEventArgs e)
{
if (userInputValid)
{
NavigateToScreen_1_2();
}
}
This strongly depends on how your next screen is used. If it is a UserControl (View) that is inserted in another part of the application lets say an ContentControl then you can just change the content of the Content control to your new screen.
Take a look at Commands in WPF in combination with MVVM. This can help you with your problem. Instead of having an OnClick event you define an execute function inside your Views ViewModel and have a public property which you can bind to your buttons Command property
like this.
Have a simple class:
public class MainViewModel
{
public MainViewModel()
{
NextViewCommand = new DelegateCommand(NextView);
}
public ICommand NextViewCommand {get;set;}
public UserControl NewView {get;set;}
public void NextView()
{
NewView = new UserControl() //set your NewView property to your new usercontrol
//Apply validation on all your binded properties you want to validate.
}
}
DelegateCommands
Wire up the ViewModel to your View using:
this.DataContext = new MainViewModel();
And connect your command to your button using:
<Button content="Next view" Command="{Binding NextViewCommand}"/>
I think this is the best way to go, and helps you.
Related
I have a button with its Click event on the code-behind, something like this:
private void ButtonManager_OnClick(object sender, RoutedEventArgs e)
{
var addButton = sender as FrameworkElement;
}
In case I move to MVVM and no code-behind, I use a Command class with Execute, something like this:
public override void Execute(object? parameter)
{
// sender?
}
How should I manage the "sender as.." that was used in code-behind?
You can use CommandParameter to bind the control to itself.
but it's not a good idea to send the ViewComponent to ViewModel and change it. because in MVVM, the View shouldn't know anything about View. If you want to change anything in View, you can define properties in ViewModel and bind them to view separately.
Any way, the solution for your question is in the code below but I don't suggest it:
Sample Code
Xaml:
<Button Command="{Binding Command}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=.}"/>
ViewModel:
private void Execute(object? obj)
{
var sender = obj as FrameworkElement;
}
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 have in my Page1.xaml.cs (code-behind) an event which needs to change all properties in my ViewModel.
Here is an example: (Page1.xaml.cs)
public Page1()
{
InitializeComponent();
example.Event += example_Event;
}
private void example_Event(...)
{
// here I want to change all Properties in my ViewModel
}
How can I achieve this?
EDIT
I have a WebBrowser-Control that displays a .ppt. I want to update all of my Properties in my ViewModel when this Event gets triggered:
xaml.cs:
private void powerPointBrowser1_LoadCompleted(object sender, NavigationEventArgs e)
{
//...
oPPApplication.SlideShowNextSlide += ppApp_SlideShowNextSlide; //Event that gets triggered when i change the Slide in my WebBrowser-Control
}
private void ppApp_SlideShowNextSlide(PPt.SlideShowWindow Wn)
{
// here i dont know how to get access to my Properties in my VM (i want to call OnChangedProperty(//for all properties in my VM))
}
Normally, View (including code behind) does not have responsibility to notify the ViewModel's properties to be updated, it should be the other way around. But, I see that in your case, you would like to do certain thing (in this case, retrieve the latest value of each properties) when handling some event, so here you are: some solutions for what you need.
In your VM, define a method that fires PropertyChanged to all properties:
public void UpdateAllProperties()
{
// Call OnPropertyChanged to all of your properties
OnPropertyChanged(); // etc.
}
Then in your View's code behind, what you need is just calling that method:
// every View has a ViewModel that is bound to be View's DataContext. So cast it, and call the public method we defined earlier.
((MyViewModel)DataContext).UpdateAllProperties();
This approach is unfortunately not very elegant for MVVM style. I would suggest that you make this method/ event handler as a Bindable ICommand. So you don't need to write any code behind, for ex: In your VM define the ICommand.
public ICommand UpdateAllPropertiesCommand {get; private set;}
= new Prism.Commands.DelegateCommand(UpdateAllProperties);
// You can switch the UpdateAllProperties method to private instead.
// Then remove any code behinds you had.
Then, in your View (xaml), you could bind the ICommand to certain control's event trigger.
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<!--In one of the controls-->
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding UpdateAllPropertiesCommand , Mode=OneTime}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Here, the command will be automatically called when handling the loaded event.
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.
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.