Fire command from custom canvas - c#

I develop application with MVVM pattern. In my viewmodel project I have custom Command. In View I have custom canvas. I want to fire command from my canvas code behind but I dont have idea how.
My app startup (startup is in other project):
private void ApplicationStartup(object sender, StartupEventArgs e)
{
// init main ViewModel
var viewModel = new MainViewModel(Container);
// init and show window
var mainWindow = new MainWindow();
mainWindow.Show();
mainWindow.DataContext = viewModel;
}
I dont want to have reference to ViewModel in View, because I need reference to View in ViewModel.
Thanks!

The way you usually do this is implement your own COMMAND in the CustomCanvas class. After that, you bind that command against command handler in viewmodel.
Just like you do with button:
<Button
Command="{Binding YourVmCommand}"
/>
Makes sense?
To implement such functionality, you need to hack through ButtonBase.cs and see how it's done.
There's a lot code involved, but the idea is very simple.
1) Create new property in your View.
public ICommand Command { get; set; }
2) Bind to it in XAML
3) Execute it in View, when you need.
if(Command != null && Command.CanExecute(parameter))
Command.Execute(parameter);

Related

MVVM: Is code-behind evil or just pragmatic?

Imagine you want a Save & Close and a Cancel & Close button on your fancy WPF MVVM window?
How would you go about it? MVVM dictates that you bind the button to an ICommand and inversion of control dictates that your View may know your ViewModel but not the other way around.
Poking around the net I found a solution that has a ViewModel closing event to which the View subscribes to like this:
private void OnLoaded(Object sender
, RoutedEventArgs e)
{
IFilterViewModel viewModel = (IFilterViewModel)DataContext;
viewModel.Closing += OnViewModelClosing;
}
private void OnViewModelClosing(Object sender
, EventArgs<Result> e)
{
IFilterViewModel viewModel = (IFilterViewModel)DataContext;
viewModel.Closing -= OnViewModelClosing;
DialogResult = (e.Value == Result.OK) ? true : false;
Close();
}
But that is code-behind mixed in with my so far very well designed MVVM.
Another problem would be showing a licensing problem message box upon showing the main window. Again I could use the Window.Loaded event like I did above, but that's also breaking MVVM, is it not?
Is there a clean way or should one be pragmatical instead of pedantic in these cases?
First, create an interface that contains only the Close method:
interface IClosable
{
void Close();
}
Next, make your window implement IClosable:
class MyWindow : Window, IClosable
{
public MyWindow()
{
InitializeComponent();
}
}
Then let the view pass itself as IClosable as command parameter to the view model:
<Button Command="{Binding CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
And lastly, the command calls Close:
CloseCommand = new DelegateCommand<IClosable>( view => view.Close() );
And what have we now?
we have a button that closes the window
we have no code in code-behind except , IClosable
the view model knows nothing about the view, it just gets an arbitrary object that can be closed
the command can easily be unit tested
There is nothing wrong or right with using code behind, this is mainly opinion based and depends on your preference.
This example shows how to close a window using an MVVM design pattern without code behind.
<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" CommandParameter="{Binding ElementName=LoginWindow}"/>
<!-- the CommandParameter should bind to your window, either by name or relative or what way you choose, this will allow you to hold the window object and call window.Close() -->
basically you pass the window as a parameter to the command. IMO your viewmodel shouldn't be aware of the control, so this version is not that good. I would pass a Func<object>/ some interface to the viewmodel for closing the window using dependency injection.
Take a look at some toolkits e.g. MVVMLight has EventToCommand, which allows you to bind command to events. I generally try my best to limit logic in View, as it's harder to test it.
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight"
...
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<command:EventToCommand Command="{Binding YourCommandInVM}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Sometimes I use a work-around.
Assumes u have a view "MainWindow" and a viewmodel "MainWindowVM".
public class MainWindowVM
{
private MainWindow mainWindow;
public delegate void EventWithoudArg();
public event EventWithoudArg Closed;
public MainWindowVM()
{
mainWindow = new MainWindow();
mainWindow.Closed += MainWindow_Closed;
mainWindow.DataContext = this;
mainWindow.Loaded += MainWindow_Loaded;
mainWindow.Closing += MainWindow_Closing;
mainWindow.Show();
}
private void MainWindow_Loaded()
{
//your code
}
private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
//your code
}
private void MainWindow_Closed()
{
Closed?.Invoke();
}
}
Here I store my view in a private variable so you can access it if you need it. It breaks a bit the MVVM.
In my viewmodel, I create a new view and show it.
Here I also capture the closing event of the view an pass it to an own event.
You can also add a method to the .Loaded and .Closing events of your view.
In App.xaml.cs you just have to create a new viewmodel object.
public partial class App : Application
{
public App()
{
MainWindowVM mainWindowVM = new MainWindowVM();
mainWindowVM.Closed += Mwvm_Close;
}
private void Mwvm_Close()
{
this.Shutdown();
}
}
I create a new viewmodel object and capture it own close-event and bind it to the shutdown method of the App.
Your description indicates that the view model is some kind of a document view. If that's correct then I would leave Save, Close, etc. to be handled by the document container e.g. the application or the main window, because these commands are on a level above the document in the same way as copy/paste are on the application level. In fact ApplicationCommands has predefined commands for both Save and Close which indicates a certain approach from the authors of the framework.

Why dynamically changed data not updated on MVVM binding here

I am using Mvvm approach in silverlight. where i try to bind a TextBox like this :
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="{Binding ViewModelText}" Grid.Row="0"></TextBlock>
</Grid>
where this xaml.cs class behind this xaml is:
public partial class UIeLementRender : UserControl
{
public UIeLementRender()
{
InitializeComponent();
this.DataContext = new ViewModel.TabControlStuffViewModel.uiElementRendererViewModel();
}
}
and viewmodel class is:
public class uiElementRendererViewModel: GenericViewModel
{
private String viewModelText;
public String ViewModelText
{
get
{
return viewModelText;
}
set
{
viewModelText = value;
OnPropertyChanged("ViewModelText");
}
}
public uiElementRendererViewModel()
{
this.viewModelText = "Hii from UIelemnt rendering"; //this updated but below one is never updated.
}
public uiElementRendererViewModel(ProgramVersion pv) //This constructor is called dynamically on abutton click from other view model class but it never updates the viewModelText
{
this.viewModelText = "Hii changed UIelemnt rendering";
this.OnPropertyChanged("ViewModelText");
}
}
Now when i run the code it shows me "Hii from UIelemnt rendering"; (which is correct) but when i press a button from another viewmodel class dynamically, i wan to update the new text on viewModelText to "Hii changed UIelemnt rendering" . Which is not updated even i have done "this.OnPropertyChanged("ViewModelText");" in my second costructor. (it is still has the text of default constructor/without parameter)
How to update this nex text which is obtained dynamically ?
You need to set the actual property, not re-initialize the whole ViewModel:
viewModel.ViewModelText = "Hii changed UIelemnt rendering";
The call to OnPropertyChanged in your constructor is redundant, as that will run before any actual binding happens.
If you -have- to re-initialize the ViewModel, and there's no possible way around this, then you also need to re-initialize the view, or manually set the DataContext of the existing view to the new instance of the ViewModel class.
I've created a few sample files that illustrate how it -could- be done: http://pastebin.com/W6Yh7N6E
I'll try to illustrate how WPF would do the databinding with a short list here:
Create View
Create ViewModel (Instantiate, run constructor, etc.) - This is where the event is currently fired in your own example
Set ViewModel as DataContext (Which in your example never happens anyways)
WPF Updates its bindings with current values (So no need for another PropertyChanged event here)
There's a bunch more stuff happening behind the scenes, but these steps should be sufficient to give you a decent understanding of why the PropertyChanged event wouldn't do anything in a constructor.
When you create a new viewmodel from you button click, you actually have two instances of the viewmodel. The UI however is still bound to the first one, so whatever changes you make in the second, will never appear on the UI. You could set the second instance as new DataContext:
private void OnButtonClick(object sender, EventArgs e)
{
this.DataContext = new ViewModel.TabControlStuffViewModel.uiElementRendererViewModel(/*pass variable here*/);
}
But this is hardly what you actually want. What you should do instead is modify the existing viewmodel object:
private void OnButtonClick(object sender, EventArgs e)
{
var vm = this.DataContext as uiElementRendererViewModel; // Get the current view model and cast it to correct type
vm.ViewModelText = "Enter new text here"; // Update the text
}

Correct approach to UserControl creation when using MVVM

This is more of a conceptual question rather than a practical one. I'm just starting to learn the MVVM concept for developing UI , and I've come across a dillema I'm not sure the answer to:
Say I have a main window and a little pop-up window (meaning it's a small window with some UI elements in it). The structure of the program will look something like this:
MainWindow
model <-- MainWindowViewModel.cs <-- MainWindowView.xaml (containing no code-behind)
PopUpWindow (A UserControl)
model <-- PopUpWindowViewModel.cs <-- PopUpWindowView.xaml (containing no code-behind)
*the model is just a bunch of BL classes that are irrelevant for this question.
Now , lets say I want to create a new PopUp window from inside the MainWindowViewModel (or even save an instance of it in a private data-member). What is the correct way of doing so?
The way I see it I can't do something like this :
PopUpWindow pop = new PopUpWindow()
Because it kind of defeats the purpose of abstracting the view from the view model(What if a year from now i'll want to create a better version of the PopUpWindow using the same PopUpWindowViewModel?).
On the other hand , I can't initialize a new instnace of the PopUpWindow using just it's view model (The viewModel as I understand is not supposed to know anything about the view that will use it).
Hope it all makes sense... so what would you do in that situation?
*Just to clarify it further , let's say for argument's sake that the situation I'm describing is a button on the MainWindowView that upon clicking will open a PopUpWindowView.
Thanks in advnace.
I had somewhat a similar dilemma and I'll explain how I solved it.
Let's say you have MainWindow and a SettingsWindow, which you want to display when the SettingsButton is clicked.
You have two respective view models, MainWindowViewModel and SettingsViewModel, which you will be passing as their Window.DataContext properties.
Your MainWindowViewModel should expose an ICommand property named SettingsButtonCommand (or similar). Bind this command to the SettingsButton.Command.
Now your command should invoke something like this:
void OnSettingsButtonClicked()
{
var viewModel = new SettingsViewModel();
var window = new SettingsWindow();
window.DataContext = viewModel;
window.Show();
}
There is a slight issue when you want to use Window.ShowDialog(), because you need to resume execution.
For these cases I have an asynchronous variant of the DelegateCommand:
public sealed class AsyncDelegateCommand : ICommand
{
readonly Func<object, Task> onExecute;
readonly Predicate<object> onCanExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public AsyncDelegateCommand(Func<object, Task> onExecute)
: this(onExecute, null) { }
public AsyncDelegateCommand(Func<object, Task> onExecute, Predicate<object> onCanExecute)
{
if (onExecute == null)
throw new ArgumentNullException("onExecute");
this.onExecute = onExecute;
this.onCanExecute = onCanExecute;
}
#region ICommand Methods
public async void Execute(object parameter)
{
await onExecute(parameter);
}
public bool CanExecute(object parameter)
{
return onCanExecute != null ? onCanExecute(parameter) : true;
}
#endregion
}
You've specifically said that the popup is a UserControl so you can use basic data templating. First create view models for your main window and popup control:
public class MainViewModel : ViewModelBase
{
private PopUpViewModel _PopUp;
public PopUpViewModel PopUp
{
get { return _PopUp; }
set { _PopUp = value; RaisePropertyChanged(() => this.PopUp); }
}
}
public class PopUpViewModel : ViewModelBase
{
private string _Message;
public string Message
{
get { return _Message; }
set { _Message = value; RaisePropertyChanged(() => this.Message); }
}
}
The MainViewModel's PopUp member is initially null, we'll set it to an instance of PopUpViewModel when we want the popup to appear. To do that we create a content control on the main window and set it's content to that member. We also use a data template to specify the type of child control to create when the popup view model has been set:
<Window.Resources>
<DataTemplate DataType="{x:Type local:PopUpViewModel}">
<local:PopUpWindow />
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Content="Show PopUp" Click="Button_Click_1" HorizontalAlignment="Left"/>
<ContentControl Content="{Binding PopUp}" />
</StackPanel>
I'm doing a big no-no here by creating the view model in the code-behind along with a click handler, but it's just for illustrative purposes:
public partial class MainWindow : Window
{
MainViewModel VM = new MainViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = this.VM;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.VM.PopUp = new PopUpViewModel { Message = "Hello World!" };
}
}
That's it! Click the button, popup window appears underneath it showing the content. Now it may not always be this simple, sometimes you may want to create multiple children on a parent control...in that case you'd set up an ItemsControl, set its panel to a Grid (say) and modify the data templates to set the Margin etc on each element to position them. Or you may not always know what type of view model is going to be created, in which case you need to add multiple data templates for each type you're expecting. Either way you still have good separation of concerns because it is the views that are deciding how to display the content in the view models. The view models themselves still don't know anything about the views and they can be unit-tested etc independently.

How to determine which View executed a command

I have a View called InformationView.xaml and this same View is re-used to display the same information from 3 different sources (each view has a different window title). Each View has their datacontext set the same instance of one ViewModel type. Within my ViewModel class, I have an ICommand property that the 'Close' button inside the View is bound to. -- Is there a way to determine who the sender was of the command? (specifically, by window title).
Here is an example:
I have a view class with the following button ("Note: each View will have a different window title / display data from a different source--but the same View is used)
<Button Width="75" Height="23" Margin="0,0,5,5" Content="Close" Command="{Binding CloseCommand}" />
I have a ViewModel class with the following command
public ICommand CloseCommand
{
get
{
if (this._closeCommand == null)
{
this._closeCommand = new RelayCommand(Command => this.OnClose());
}
return _closeCommand;
}
}
I am looking for a way to determine which window executed the command (I will have multiple instances of the View using the same ViewModel).
I'm not sure if I understand you correctly. However, you could possibly implement the Unloaded event. Set a breakpoint inside that event method and when you hit the breakpoint. You could check the window title property for that view.
What about just making the Close() method public so that other objects can specify what the close behavior should be?
Something along the lines of this in your InformationViewModel:
public event EventHandler RequestClose;
void OnRequestClose()
{
EventHandler handler = this.RequestClose;
if (handler != null)
handler(this, EventArgs.Empty);
}
Then you can use it from within your other view models like this:
InformationViewModel.Close += CloseMethod;
public CloseMethod(object sender, EventArgs e)
{
// Implement close logic here
}

Property in ViewModel not displayed in View

I have a ViewModel called MainWindowViewModel. In this I have a property that shows a modal window when an error occurs. This works fine as long as an error occurs during start-up.
When an error occurs after start-up, in SubViewModel, I invoke the parametrized constructor in MainWindowViewModel.
MainWindowViewModel.cs
public MainWindowViewModel()
{
if (!isServerRunning)
{
this.ModalWindow = new LogViewModel("Server is down.");
}
else
{
this.ModalWindow = new LogViewModel();
}
}
public MainWindowViewModel(string logMessage)
{
this.ModalWindow = new LogViewModel(logMessage);
}
public LogViewModel ModalWindow
{
get
{
return _modalWindow;
}
set
{
_modalWindow = value;
OnPropertyChanged("ModalWindow");
}
}
MainWindow.xaml
....
<Grid>
<vw:LogView Content="{Binding Path=ModalWindow}"/>
</Grid>
MainWindowViewModel is bound to MainWindow.xaml
SubViewModel is bound to SubView.xaml
MainWindow.xaml loads multiple views, one of them is SubView.
In App.xaml I have created an instance of the ViewModel and bound it to MainWindow.
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow mainWindow = new MainWindow();
MainWindowViewModel viewModel = new MainWindowViewModel();
mainWindow.DataContext = viewModel;
mainWindow.Show();
}
What I realized was that the modal window shows up when an error occurs after start-up if I create the modal window property in SubViewModel and bind it to SubView. But this is not ok since SubView is only a DockPanel covering 1/4 of the MainWindow. I.e. 1/4 is only covered with a modal window instead of the whole MainWindow.
I am not sure why modal window does not appear in MainWindow when I call the parametrized constructor from SubViewModel. When I debug I see that the part _modalWindow = value; has correct values but in any case the modal window does not show up.
I am binding the ViewModel to the Datacontext of the MainWindow. That's is probably why I see the Modal window when error occurs on start-up. For errors after start-up: Must I (from SubViewModel where I invoke the parametrized constructor in MainWindowViewModel) do some kind of binding to the datacontext of the Mainwindow again? How is the best way of doing this without having to create a new instance of MainWindow? Because MainWindow should only be created once.
Any help is much appreciated.
It looks like you are recreateing the ViewModel every time an error occours. If so you would need to reset the binding in the view as well, which would defeat the purpose of MVVM.
Rather have one instance of the ViewModel and propagate the errors to the View using OnPropertyChanged().
There are of course many ways of doing this, but I usually keep a reference to the ViewModel in my View, and then a reference to the Model in the ViewModel. This way the Model is completely decoupled from the View/ViewModel and likewise the ViewModel is decoupled from the View.
You should not create MainWindowViewModel again and again. It should be created once and set to the datacontext of the mainwindow.
The problem is with the approach that you are taking to show dialog boxes. This is making things complicated.
The best solution to show dialog boxes is to use mvvmlight toolkit's messenger. Check this for some hints on it's usage.
This is how you could use mvvmlight toolkit's messenger to show dialogboxes:
View:
Messenger.Default.Register<DialogMessage>(
view,
msg =>
{
var result = MessageBox.Show(
msg.Content,
msg.Caption,
msg.Button,
msg.Icon);
}
);
ViewModel:
private void ShowMessageBox(string msgStr, string capStr, MessageBoxButton btn, MessageBoxImage img)
{
var message = new DialogMessage(msgStr, null)
{
Button = btn,
Caption = capStr,
Icon = img
};
Messenger.Default.Send(message);
}
Just call the above method (ShowMessageBox) from any viewmodel to show a dialogbox.

Categories

Resources