I'm working on a WPF application using the MVVM pattern and I'm still fairly new to .NET development. My understanding is that the View should set its data context to a ViewModel and then any data related processing should be done in the ViewModel while the UI part should be handled in the view (XAML or code behind).
So I have a menu with each menu item bound to a DelegateCommand (using Prism) declared and handled in the ViewModel with keyboard shortcuts and it works flawlessly. However, I wanted to bind a menu item to a command in the View's code behind file as it doesn't manupulate any data (it just shows or hide a panel).
View (XAML)
<Window x:Class="Editor.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Editor.Views"
xmlns:vm="clr-namespace:Editor.ViewModels"
mc:Ignorable="d"
x:Name="RootWindow"
WindowStartupLocation="CenterScreen"
Width="1200" Height="650">
<!-- Data Context -->
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<!-- Keyboard Shortcuts -->
<Window.InputBindings>
<KeyBinding Modifiers="Control" Key="L" Command="{Binding ElementName=RootWindow, Path=ToggleLayersCommand}" />
</Window.InputBindings>
<!-- Main Menu -->
<Menu>
<MenuItem Header="View" Padding="5, 2">
<MenuItem Header="Toggle Layers Panel" InputGestureText="CTRL + L" Command="{Binding ElementName=RootWindow, Path=ToggleLayersCommand}" />
</MenuItem>
</Menu>
</Window>
View (Code behind)
public partial class MainWindow : Window
{
public DelegateCommand ToggleLayersCommand { get; private set; }
public MainWindow()
{
InitializeComponent();
ToggleLayersCommand = new DelegateCommand(ToggleLayersCommand_OnExecuted, () => true);
}
private void ToggleLayersCommand_OnExecuted()
{
LayerListPanel.Visibility = (LayerListPanel.Visibility == Visibility.Collapsed) ? Visibility.Visible : Visibility.Collapsed;
}
}
I named the window in XAML to find the command in the View instead of the ViewModel when binding the Command attribute. It seems to find it since I'm getting intellisense but it never fires.
I could use a click event instead even though I'd rather use a command but then how to bind the keyboard shortcut to the event?
I would define the command inside the ViewModel and have it change a property public Visibility LayerListPanelVisibility (which should also be defined in the ViewModel). Then I would bind LayerListPanel.Visibility to this property.
Keep your code-behind as empty as possible.
The reason that the command is not found by your Binding is that ToggleLayersCommand is null when the Binding is resolved. It is only shortly after binding resolution that you assign the proper command to ToggleLayersCommand. However, the Binding will not be updated as your Viewmodel does not raise a PropertyChanged event.
If you want to keep your command in the View, you can either raise a PropertyChanged event when you have assigned the command or you assign the command before you call InitializeComponent:
public MainWindow()
{
ToggleLayersCommand = new DelegateCommand(ToggleLayersCommand_OnExecuted, () => true);
InitializeComponent();
}
I had same problem with binding command to keybinding. All I have done is give to a button its own name and from view code behind set command to yours.
My example looks like so:
<KeyBinding x:Name="ShowDetailsKeyBinding"
Key="D"
Modifiers="Control" />
Code behind:
ShowDetailsKeyBinding.Command = new DelegateCommand(ShowDetailsOperation);
I use this solution to run my animations, other way you should create command in yours ViewModel.
You have to use the ViewModel for command and not the View:
ViewModel:
public partial class MainViewModel
{
public DelegateCommand ToggleLayersCommand { get; private set; }
public MainViewModel()
{
ToggleLayersCommand = new DelegateCommand(ToggleLayersCommand_OnExecuted, () => true);
}
private void ToggleLayersCommand_OnExecuted()
{
LayerListPanel.Visibility = (LayerListPanel.Visibility == Visibility.Collapsed) ? Visibility.Visible : Visibility.Collapsed;
//THIS WILL PROBABLY NOT WORK...
//You can use another public property to change your visibility.
// Create a public visibility and bind it to the correct item
}
}
XAML:
<Menu>
<MenuItem Header="View" Padding="5, 2">
<MenuItem Header="Toggle Layers Panel" InputGestureText="CTRL + L" Command="{Binding Path=ToggleLayersCommand}" />
</MenuItem>
</Menu>
Related
I'm using ViewModel-first aprroach. In MainWindow.xaml I've set ContentControl where I display my UserControls (Views), by a click on a MenuItem. When I click to display UserControl first time, everything works fine.
But when I click same MenuItem to open It once again, my UserControl displays again but doesn't get loaded anymore, resulting in not having refreshed bindings. Setting my ContentControl's Content to null doesn't resolve issue.
My whole setup is like this:
1.) App.xaml resource
<!--DataContext for MainWindow.xaml-->
<ViewModels:MainWindowViewModel x:Key="Main_VM"/>
<!--DataTemplate for UserControl-->
<DataTemplate DataType="{x:Type ViewModels:MyViewModel}">
<Views:MyView />
</DataTemplate>
2.) MainWindow.xaml, where my ContenControl is located
<Window x:Class="My.Views.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{StaticResource Main_VM}">
<Grid>
<!--Menu which opens view on command-->
<Menu VerticalAlignment="Top" IsMainMenu="True" >
<MenuItem Header="My View" Command="{Binding Show_View}" CommandParameter="1"/>
</Menu>
<ContentControl Content="{Binding Display_View}" />
<!--And all other controls, like Menu for opening views on click...-->
</Grid>
</Window>
3.) ViewModel for Mainwindow.xaml (inherited from BaseViewModel)
public class MainWindowViewModel : BaseViewModel
{
public MainWindowViewModel()
{
//Command for displaying Views
Show_View = new Relay_Command(Open_view, null);
}
public ICommand Show_View { get; set; }
private BaseViewModel _display_view;
public BaseViewModel Display_View
{
get { return _display_view; }
set { _display_view = value; OnPropertyChanged(); }
}
private void Open_view(object parameter)
{
Display_View = null; //This doesn't help at all!!!
switch (parameter)
{
case "1":
Display_View= new MyViewModel();
break;
}
}
}
4.) And my UserControl.xaml
<UserControl x:Class="MyProject.Views.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<!--Event-->
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<!--Calling a method on Load (firing only first tme !!)-->
<ei:CallMethodAction MethodName="MethodForRetrievingData" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<!--Controls in UserControl for binding etc...-->
</Grid>
</UserControl>
I've tried debugging, but as told, Loaded event of UserControl happens only once. I'm out of ideas on this one, looks like my design has a flaw.
What could be a problem here, maybe I'm missing something like NotifyProperty on UserControl itself?
You need to actually unload the view for it to be loaded again. Setting the source property of the ContentControl's Content property to null just before setting it to another MyViewModel won't unload the view. The DataTemplate is "cached".
Why don't you call the MethodForRetrievingData from the view model itself instead of relying on the view raising a Loaded event? You may for example initialize it asynchronously.
Instead of displaying a MyViewModel object content using template, try display a MyView content instead.
So you would have
private MyView _display_view;
public MyView Display_View
{
get { return _display_view; }
set { _display_view = value; OnPropertyChanged(); }
}
private void Open_view(object parameter)
{
Display_View = null; //This doesn't help at all!!!
switch (parameter)
{
case "1":
Display_View= new MyView(); // Or assign a view model here: {DataContext=new MyViewModel()}
break;
}
}
I have a WPF project in which I am trying to enable/disable keyboard shortcuts based on the state of public property from my viewmodel. Maybe there is a super simple solution to this, but I'm new to WPF and I couldn't find anything from google. Here is my working XAML:
<KeyBinding Modifiers="Control" Key="p" Command="{Binding PrintCommand}" CommandParameter="{Binding OpenEvent}"/>
Here is what I would like to do:
<KeyBinding Modifiers="Control" Key="p" Command="{Binding PrintCommand}" CommandParameter="{Binding OpenEvent}" IsEnabled="{Binding IsOnline}"/>
Basically, I'm wondering if there is something similar to the "IsEnabled" property of WPF buttons that I can apply to this. I have about 20 different shortcuts that are depending on this variable. I could go into the code behind for each of the 20 commands and add logic, but that seems fairly kludgy and I'm thinking there has to be a better way. I've seen solutions using "CanExecute", but that is for commands of type ICommand and I'm using commands of type RelayCommand.
You can use the mvvm-light RelayCommand CanExecute in your KeyBinding Commands. Heres a simple example where I have blocked the use of the P Key based on SomeProperty
MainViewModel.cs
private bool someProperty = false;
public bool SomeProperty
{
get { return someProperty = false; }
set { Set(() => SomeProperty, ref someProperty, value); }
}
private RelayCommand someCommand;
public RelayCommand SomeCommand
{
get
{
return someCommand ??
new RelayCommand(() =>
{
//SomeCommand actions
}, () =>
{
//CanExecute
if (SomeProperty)
return true;
else
return false;
});
}
}
and the Binding on the front end
MainWindow.xaml
<Window x:Class="WpfApplication12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
<Window.InputBindings>
<KeyBinding Key="P" Command="{Binding SomeCommand}" />
</Window.InputBindings>
<Grid>
<TextBox Width="200" Height="35" />
</Grid>
Hope it helps
Use the command's CanExecute method on your view-model.
Then you could remove your IsEnabled property within your XAML.
I want to write an HTML Editor. For this I want to have a MenuItem "New", which opens a WPF WebBrowser Control in a Dockpanel when it is clicked. Since now, I implement this function with CodeBehind. So my XML Code looks like this:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="661.94" Width="781.716">
<DockPanel x:Name="DockPanel1" HorizontalAlignment="Left" Height="629"
LastChildFill="False" VerticalAlignment="Top" Width="772">
<Menu DockPanel.Dock="Top" HorizontalAlignment="Right" Width="772">
<MenuItem Header="_Webseite">
<MenuItem Header="_Neu" Click="Neu_Click" />
<MenuItem Header="_Öffnen" Click="Oeffnen_Click"/>
<MenuItem Header="_Speichern" Click="Speichern_Click"/>
<Separator HorizontalAlignment="Left" Width="145"/>
<MenuItem Header="_Schließen" HorizontalAlignment="Left" Width="145"/>
</MenuItem>
<MenuItem Header="_Tools">
<MenuItem Header="_Button" Click="Select_Button"> </MenuItem>
</MenuItem>
</Menu>
<StackPanel></StackPanel>
</DockPanel>
And in the Code behind there is the following function implemented:
public partial class MainWindow : Window
{
public static IHTMLDocument2 doc;
public volatile WebBrowser webBrowser;
public MainWindow()
{
InitializeComponent();
}
private void Neu_Click(object sender, RoutedEventArgs e)
{
// create new WebBrowser for editing
webBrowser = new WebBrowser();
DockPanel.SetDock(webBrowser, Dock.Top);
this.DockPanel1.Children.Add(webBrowser);
string text = "<html><body></body></html>";
File.WriteAllText(#"C:\tmp\file.html", text);
webBrowser.Navigate("file:///C:/tmp/file.html");
doc = webBrowser.Document as IHTMLDocument2;
doc.designMode = "On";
}
But now I want to separate the View and the Model by using the MVVM pattern. Can anyone help me how to do it? I have real problems to understand the MVVM pattern with my Application.
Thanks for helping!
I handle methods on Controls with Command binding and the MVVM Light messenger class. In order for this to work you'll need to install the MVVM Light Nuget packages for WPF. With this approach the Click event on your MenuItem is bound to a RelayCommand in the ViewModel. That RelayCommand broadcasts a message ("MakeWebBrowser") which is accessible by any class subscribed to the messaging service. The View codebehind is subscribed to messaging, receives the message and fires the method that makes your WebBrowser.
View:
<MenuItem Header="_Neu" Command="{Binding MakeWebBrowserCommand}" />
ViewModel:
Declare the RelayCommand:
RelayCommand MakeWebBrowserCommand
{
get;
private set;
}
In your ViewModel constructor:
DoSomethingCommand = new RelayCommand(MakeWebBrowserCommandExecute);
Define the MakeWebBrowserCommandExecutemethod:
private void MakeWebBrowserCommandExecute()
{
Messenger.Default.Send(new NotificationMessage("MakeWebBrowser"));
}
View codebehind:
In your View's constructor, register for NotificationMessages:
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
Define the NotificationMessageReceived method:
private void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == "MakeWebBrowser")
MakeWebBrowser();
}
Rename/define the method:
private void Neu_Click(object sender, RoutedEventArgs e)
to:
private void MakeWebBrowser()
I have an issue with switching views in a WPF MVVM app.
When clicking on menu items defined in the main view, switching works fine.
When clicking on a button in a child view, switching does not work as expected.
If I set contentcontrol in child view (where the button is) as well as parent view,
the child view gets displayed mixed with previous displayed view, a button from one view and background from the one I want to switch to.
Without it, the debugger shows something happening, similar steps in the ViewModelBase class to what happened when choosing from the menu mentioned above but no visual changes in the window.
I have commands in a ViewmodelBase (that all viewmodels inherit from either directly or through a mainviewmodel) class that gets called from bindings such as in the XAML above.
CurrentViewModel is a property in ViewModelBase that is used to determine which view gets displayed. In the constructor of ViewModelBase i set commands for example:
CategoryVMCommand = new RelayCommand(() => ExecuteCategoryVMCommand());
(RelayCommand from the line above comes from the MVVM light framework,
although its not necessary for the solution to use that framework)
I found many tutorials and answers for similar problems, but couldnt get any of them to work. For example I tried, without success, using IOC for a similar problem in the below link:
MVVM Main window control bind from child user control
Here are some of the code involved and description of what Im doing:
Main Window:
<Grid>
<ContentControl Content="{Binding CurrentViewModel}" />
<DockPanel Margin="0,0,0,50">
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Open" Command="{Binding CategoryVMCommand}"/>
<MenuItem Header="_Close"/>
<MenuItem Header="_Save"/>
</MenuItem>
<MenuItem Header="_New">
<MenuItem Header="_Create" Command="{Binding MainControlVMCommand}"/>
</MenuItem>
</Menu>
<StackPanel></StackPanel>
</DockPanel>
</Grid>
</Window>
Then I select Menu item New, the following view is displayed:
<UserControl x:Class="WpfApplication1.MainControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<!--<ContentControl Content="{Binding CurrentViewModel, Mode=OneWay}" />-->
<TextBlock HorizontalAlignment="Left" Margin="10,20,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="39" Width="144" FontSize="24"><Run Language="en-gb" Text="TITLE"/><LineBreak/><Run Language="en-gb"/></TextBlock>
<Button Content="Open category" HorizontalAlignment="Left" Margin="10,136,0,0" VerticalAlignment="Top" Width="153" Height="63" Command="{Binding CategoryVMCommand}" />
<Button Content="Create new category" HorizontalAlignment="Left" Margin="10,218,0,0" VerticalAlignment="Top" Width="153" Height="63"/>
<ListBox HorizontalAlignment="Left" Height="145" Margin="293,136,0,0" VerticalAlignment="Top" Width="201" Background="#FFDDDDDD"/>
<TextBlock HorizontalAlignment="Left" Margin="293,107,0,0" TextWrapping="Wrap" Text="Recently Used" VerticalAlignment="Top" FontSize="18"/>
</Grid>
</UserControl>
button open category clicked, and Currentviewmodel set code executes (depending on ContenControl in MainControl view being commented out or not either
return or assigned), then The ExecuteCategoryCommand get executed. Then the line with the expected command in ViewModelBase constructor executes, although
either no change or the mixed result i mentioned originally
ViewModelBase class:
namespace ViewModel
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ViewModelBase _currentViewModel;
public ICommand CategoryVMCommand { get; private set; }
public ICommand MainControlVMCommand { get; private set; }
protected void NotifyPropertyChanged( String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ViewModelBase()
{
MainControlVMCommand = new RelayCommand(() => ExecuteMainControlVMCommand());
CategoryVMCommand = new RelayCommand(() => ExecuteCategoryVMCommand());
}
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if (_currentViewModel == value)
return;
_currentViewModel = value;
NotifyPropertyChanged("CurrentViewModel");
}
}
protected void ExecuteCategoryVMCommand()
{
CurrentViewModel = null;
CurrentViewModel = new CategoryVM();
}
protected void ExecuteMainControlVMCommand()
{
CurrentViewModel = null;
CurrentViewModel = new MainControlVM();
}
}
}
So my question is how can I click the button in the child view, send command from ViewModelBase, set CurrentViewModel, and successfully switch views within one window without any visual remains of the previously displayed view?
Thanks for any help.
I created an attached property, AttachedBehaviorsManager.Behaviors that is to be used as an MVVM helper class that ties events to commands. The property is of type BehaviorCollection (a wrapper for ObservableCollection). My issue is that the Binding for the Behavior's Command always winds up being null. When used on the buttons it works just fine though.
My question is why am I losing my DataContext on items inside of the collection, and how can I fix it?
<UserControl x:Class="SimpleMVVM.View.MyControlWithButtons"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviors="clr-namespace:SimpleMVVM.Behaviors"
xmlns:con="clr-namespace:SimpleMVVM.Converters"
Height="300" Width="300">
<StackPanel>
<Button Height="20" Command="{Binding Path=SetTextCommand}" CommandParameter="A" Content="Button A" />
<Button Height="20" Command="{Binding Path=SetTextCommand}" CommandParameter="B" Content="Button B"/>
<TextBox x:Name="tb" Text="{Binding Path=LabelText}">
<behaviors:AttachedBehaviorsManager.Behaviors>
<behaviors:BehaviorCollection>
<behaviors:Behavior Command="{Binding Path=SetTextCommand}" CommandParameter="A" EventName="GotFocus"/>
</behaviors:BehaviorCollection>
</behaviors:AttachedBehaviorsManager.Behaviors>
</TextBox>
</StackPanel>
You bind to the command because this is using the MVVM (Model-View-ViewModel) pattern. The datacontext of this user control is a ViewModel object containing a property that exposes the command. Commands do not need to be public static objects.
The buttons in the shown code have no problem executing. They are bound to to the SetTextCommand in the viewmodel:
class MyControlViewModel : ViewModelBase
{
ICommand setTextCommand;
string labelText;
public ICommand SetTextCommand
{
get
{
if (setTextCommand == null)
setTextCommand = new RelayCommand(x => setText((string)x));
return setTextCommand;
}
}
//LabelText Property Code...
void setText(string text)
{
LabelText = "You clicked: " + text;
}
}
The problem is that the binding to the same SetTextCommand that works in the buttons is not recognized in the behavior:Behavior.
Why are you binding to the command? Commands are meant to be setup this way:
<Button Command="ApplicationCommands.Open"/>
Suppose you define a command class like so:
namespace SimpleMVVM.Behaviors {
public static class SimpleMvvmCommands {
public static RoutedUICommand SetTextCommand { get; }
}
}
You would use it like so:
<Button Command="behaviors:SimpleMvvmCommands.SetTextCommand"/>
The MVVM pattern isn't applicable the way you're using it. You'd put the command handler on the VM, but commands themselves are meant to be in the static context. Please refer to the documentation on MSDN for further information.