WPF MenuItem grayed out when binding Command - c#

I'm trying to hook up commands to context menu items in a TaskbarIcon but everytime I do, they become grayed out.
Here's the XAML:
<ResourceDictionary
xmlns:local="clr-namespace:Stickie.StickieNotes.WPFGUI"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tb="http://www.hardcodet.net/taskbar"
>
<!-- Globally declared notify icon -->
<tb:TaskbarIcon x:Key="MyNotifyIcon">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="Open Settings" Command="local:App.OpenSettingsCommand"
CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
<MenuItem Header="New Note"/>
<MenuItem Header="Exit" Command="local:App.ExitApplicationCommand"
CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
And my backing CS:
namespace Stickie.StickieNotes.WPFGUI
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
static App()
{
initializeCommands();
}
static void initializeCommands()
{
Type ownerType = typeof (App);
OpenSettingsCommand = new RoutedCommand("OpenSettings", ownerType);
ExitApplicationCommand = new RoutedCommand("ExitApplication", ownerType);
CommandBinding openSettings = new CommandBinding(OpenSettingsCommand, OpenSettingsExecuted, OpenSettingCanExecute);
CommandBinding exitApplication = new CommandBinding(ExitApplicationCommand, ExitApplicationExecuted, ExitApplicationCanExecute);
CommandManager.RegisterClassCommandBinding(ownerType,openSettings);
CommandManager.RegisterClassCommandBinding(ownerType,exitApplication);
}
public static RoutedCommand OpenSettingsCommand;
public static RoutedCommand ExitApplicationCommand;
private static void ExitApplicationCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private static void OpenSettingCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private static void ExitApplicationExecuted(object sender, ExecutedRoutedEventArgs e)
{
Application.Current.Shutdown(0);
}
private static void OpenSettingsExecuted(object sender, ExecutedRoutedEventArgs e)
{
if (Application.Current.MainWindow != null)
{
Application.Current.MainWindow.Show();
}
}
}
}
I've been playing around with it and looking around a ton but I can't seem to get it to work. Does anyone have a possible solution?

From this article:
ContextMenus are separate windows with their own VisualTree and LogicalTree.
[...] the CommandManager searches for CommandBindings within the current focus scope. If the current focus scope has no command binding, it transfers the focus scope to the parent focus scope. When you startup your application the focus scope is not set. You can check this by calling FocusManager.GetFocusedElement(this) and you will receive null.
The simplest solution is to initially set the logical focus:
public Window1()
{
InitializeComponent();
// Set the logical focus to the window
Focus();
}
Another solution is to manually bind the CommandTarget to the parent ContextMenu.
<MenuItem Header="Cut" Command="Cut" CommandTarget="
{Binding Path=PlacementTarget,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}}"/>

Related

How to bind hotkeys to generic buttons?

I have a Usercontrol that creates buttons at run-time. I have made the buttons work with command bindings by clicking the buttons is there a way to trigger the buttons using hotkeys e.g ctrl+R and once I get to the TakeC command I need to know what command was pressed?
XAML:
<UserControl.InputBindings>
<KeyBinding Command="{Binding TakeC, Source=self}"
CommandParameter="{Binding }"
Gesture="CTRL+R" />
</UserControl.InputBindings>
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding CButtons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Name="CButtonsPanel" CanVerticallyScroll="true" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Name="cTakeC" Content="{Binding Content}" Command="{Binding Path=TakeCCommand}" CommandParameter="{Binding}" Margin="5">
<FrameworkElement.Style>
<MultiBinding Converter="{StaticResource CButtonStyleConverter}">
<MultiBinding.Bindings>
<Binding/>
<Binding Path="IsActive"/>
</MultiBinding.Bindings>
</MultiBinding>
</FrameworkElement.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
C#:
TakeCCommand = new RelayCommand(TakeC);
void TakeC(object parameter)
{
ButtonViewModel<StyledButtonModel> myClass = parameter as ButtonViewModel<StyledButtonModel>;
// All buttons gets here once clicked
// I need to know what key was pressed here
}
public class StyledButtonModel
{
public string Name { get; set; } = "Ctrl+R"
public CButtonStyle Styles { get; set; }
}
KeyBindings will only work when the element to which you have applied them is focused:
Keybindings without any focus
Depending on your requirements, a better approach may be to handle the PreviewKeyDown event for the parent window of the UserControl. Something like this:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
Loaded += UserControl1_Loaded;
Unloaded += UserControl1_Unloaded;
}
private void UserControl1_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= UserControl1_Loaded;
Window window = Window.GetWindow(this);
window.PreviewKeyDown += Window_PreviewKeyDown;
}
private void UserControl1_Unloaded(object sender, RoutedEventArgs e)
{
Unloaded -= UserControl1_Unloaded;
Window window = Window.GetWindow(this);
window.PreviewKeyDown -= Window_PreviewKeyDown;
}
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.R && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.LeftCtrl)))
{
var viewModel = DataContext as YourViewModel;
if (viewModel != null)
viewModel.TakeCCommand.Execute();
}
}
}
Then the "hotkey" will work regardess of what element is currently focused in the window.
You may wrap this in an attached behaviour for reuse across several UserControls but how to this is another story that is not directly related to your actual question.

WPF MenuItem.IsChecked binding not working [duplicate]

This question already has answers here:
DependencyProperty not triggered
(2 answers)
DependencyProperty getter/setter not being called
(2 answers)
Why are .NET property wrappers bypassed at runtime when setting dependency properties in XAML?
(1 answer)
Closed 5 years ago.
I have a simple menu and would like the IsChecked property of the MenuItems to be bound so that I could later manipulate them in code behind. I have a break point set in the getter and setter of the property but they are never hit.
I've searched other questions and none have shed any light on my issue. I created a skeleton project to demonstrate what I'm seeing.
MainWindow.xaml:
<Window x:Class="POC_BindingMenuItemIsChecked.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:POC_BindingMenuItemIsChecked"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MainWindowViewModel x:Key="mainWindowViewModel"/>
</Window.Resources>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Choice">
<MenuItem Header="Alpha"
IsCheckable="True"
Checked="MenuItemAlpha_Checked"
IsChecked="{Binding AlphaIsRecording, Mode=TwoWay}"/>
<MenuItem Header="Bravo"
IsCheckable="True"
Checked="MenuItemBravo_Checked"
IsChecked="{Binding
Source={StaticResource mainWindowViewModel},
Path=BravoIsRecording,
Mode=TwoWay}"/>
<MenuItem Header="Charlie"
IsCheckable="True"
Checked="MenuItemCharlie_Checked"
IsChecked="{Binding CharlieIsRecording, Mode=TwoWay}"/>
</MenuItem>
</Menu>
</DockPanel>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private MainWindowViewModel _MainWindowViewModel = null;
public MainWindow()
{
InitializeComponent();
// get a reference to the binding sources so we can set the properties
_MainWindowViewModel = new MainWindowViewModel();
this.DataContext = _MainWindowViewModel;
}
private void MenuItemAlpha_Checked(object sender, RoutedEventArgs e)
{
//no-op yet
}
private void MenuItemBravo_Checked(object sender, RoutedEventArgs e)
{
//no-op yet
}
private void MenuItemCharlie_Checked(object sender, RoutedEventArgs e)
{
//no-op yet
}
}
MainWindowViewModel.cs:
class MainWindowViewModel : DependencyObject
{
// Alpha
public static PropertyMetadata AlphaIsRecordingPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty AlphaIsRecordingProperty
= DependencyProperty.Register(
"AlphaIsRecording",
typeof(bool),
typeof(MainWindowViewModel),
AlphaIsRecordingPropertyMetadata);
public bool AlphaIsRecording
{
get { return (bool)GetValue(AlphaIsRecordingProperty); }
set { SetValue(AlphaIsRecordingProperty, value); }
}
// Bravo
public static PropertyMetadata BravoIsRecordingPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty BravoIsRecordingProperty
= DependencyProperty.Register(
"BravoIsRecording",
typeof(bool),
typeof(MainWindowViewModel),
BravoIsRecordingPropertyMetadata);
public bool BravoIsRecording
{
get { return (bool)GetValue(BravoIsRecordingProperty); }
set { SetValue(BravoIsRecordingProperty, value); }
}
// Charlie
public static PropertyMetadata CharlieIsRecordingPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty CharlieIsRecordingProperty
= DependencyProperty.Register(
"CharlieIsRecording",
typeof(bool),
typeof(MainWindowViewModel),
CharlieIsRecordingPropertyMetadata);
public bool CharlieIsRecording
{
get { return (bool)GetValue(CharlieIsRecordingProperty); }
set { SetValue(CharlieIsRecordingProperty, value); }
}
}
EDIT:
Because of the solution that mm8 provided (POCO and not Dependency) I've modified the solution. It now functions as a set of three MenuItems that behave as radio buttons. The code follows:
MainWindow.xaml:
<Window x:Class="POC_BindingMenuItemIsChecked.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:POC_BindingMenuItemIsChecked"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MainWindowViewModel x:Key="mainWindowViewModel"/>
</Window.Resources>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Choice">
<MenuItem Header="Alpha"
IsCheckable="True"
Checked="MenuItemAlpha_Checked"
IsChecked="{Binding AlphaIsRecording, Mode=TwoWay}"/>
<MenuItem Header="Bravo"
IsCheckable="True"
Checked="MenuItemBravo_Checked"
IsChecked="{Binding BravoIsRecording, Mode=TwoWay}"/>
<MenuItem Header="Charlie"
IsCheckable="True"
Checked="MenuItemCharlie_Checked"
IsChecked="{Binding CharlieIsRecording, Mode=TwoWay}"/>
</MenuItem>
</Menu>
</DockPanel>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private MainWindowViewModel _mainWindowViewModel = null;
public MainWindow()
{
InitializeComponent();
// get a reference to the binding sources so we can set the properties
_mainWindowViewModel = new MainWindowViewModel();
this.DataContext = _mainWindowViewModel;
}
private void MenuItemAlpha_Checked(object sender, RoutedEventArgs e)
{
_mainWindowViewModel.BravoIsRecording = false;
_mainWindowViewModel.CharlieIsRecording = false;
}
private void MenuItemBravo_Checked(object sender, RoutedEventArgs e)
{
_mainWindowViewModel.AlphaIsRecording = false;
_mainWindowViewModel.CharlieIsRecording = false;
}
private void MenuItemCharlie_Checked(object sender, RoutedEventArgs e)
{
_mainWindowViewModel.AlphaIsRecording = false;
_mainWindowViewModel.BravoIsRecording = false;
}
}
ViewModelBase.cs:
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
MainWindowViewModel.cs:
class MainWindowViewModel : ViewModelBase
{
private bool _alphaIsRecording = false;
private bool _bravoIsRecording = false;
private bool _charlieIsRecording = false;
// Alpha
public bool AlphaIsRecording
{
get { return _alphaIsRecording; }
set
{
_alphaIsRecording = value;
OnPropertyChanged("AlphaIsRecording");
}
}
// Bravo
public bool BravoIsRecording
{
get { return _bravoIsRecording; }
set
{
_bravoIsRecording = value;
OnPropertyChanged("BravoIsRecording");
}
}
// Charlie
public bool CharlieIsRecording
{
get { return _charlieIsRecording; }
set
{
_charlieIsRecording = value;
OnPropertyChanged("CharlieIsRecording");
}
}
}
You are binding to AlphaIsChecked and BravoIsChecked but I don't see any such properties? Try to bind to BravoIsRecording:
<MenuItem Header="Bravo"
IsCheckable="True"
Checked="MenuItemBravo_Checked"
IsChecked="{Binding
Source={StaticResource mainWindowViewModel},
Path=BravoIsRecording,
Mode=TwoWay}"/>
Also, a view model doesn't typically inherit from DependencyObject and define dependency properties. Turn your properties into ordinary CLR properties and put a breakpoint in the setter of the BravoIsRecording and it should get hit when you check the CheckBox.

WPF bind a dynamic generated slider to function

First: Not a duplicate of Binding Button click to a method --- it's about button, and Relay command can't pass the arguments I need
Also, not a duplicate of How do you bind a Button Command to a member method? - it's a simple method with no arguments - nothing to do with my question.
Obviously (but just to make sure and avoid trolls) not a duplicate of this either Silverlight MVVM: where did my (object sender, RoutedEventArgs e) go?.
Now after clearing this (sorry, I am just really sick of being marked as "duplicate" by people who didn't understand my question), let's talk about the issue: :D
I am trying to bind a generated slider (using data template) to an event (value changed), I know it's impossible to bind an event and I must use ICommand, but I don't know how to get the event arguments to the command function, this is the xaml relevant code: (without the binding since it doesnt work)
<Slider Grid.Column="1" Grid.Row="1" Height="30" IsSnapToTickEnabled="True" Maximum="100" SmallChange="1" IsMoveToPointEnabled="True"/>
And this is the function I want it to be binded to:
public void vibrationSlider_move(object Sender, RoutedPropertyChangedEventArgs<double> e)
{
VibrationValue = (byte)e.NewValue;
SendPacket(cockpitType, (byte)Index.VibrationSlider, VibrationValue);
}
As you can see, I need to use the 'e' coming with the event, I have no idea how to reach it without using the "ValueChanged" slider event.
Notes:
Please don't tell me to add the "ValueChanged" attribute like this:
<Slider ValueChanged="VibrationSlider_move"/>
:)
It's a generated dynamic slider using DataTemplate with an observableCollection, the function isn't in the window.cs file, therefore just using an event is not possible.
Thank you.
You can use the MVVMLight Toolkit, which allows to send the EventArgs as CommandParameter to the ViewModel:
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<cmd:EventToCommand Command="{Binding ValueChangedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In your command.Execute method, you now get an object as parameter which you just have to parse to the correct type...
You could create an extension
public partial class Extensions
{
public static readonly DependencyProperty ValueChangedCommandProperty = DependencyProperty.RegisterAttached("ValueChangedCommand", typeof(ICommand), typeof(Extensions), new UIPropertyMetadata((s, e) =>
{
var element = s as Slider;
if (element != null)
{
element.ValueChanged -= OnSingleValueChanged;
if (e.NewValue != null)
{
element.ValueChanged += OnSingleValueChanged;
}
}
}));
public static ICommand GetValueChangedCommand(UIElement element)
{
return (ICommand)element.GetValue(ValueChangedCommandProperty);
}
public static void SetValueChangedCommand(UIElement element, ICommand value)
{
element.SetValue(ValueChangedCommandProperty, value);
}
private static void OnSingleValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
var element = sender as Slider;
var command = element.GetValue(ValueChangedCommandProperty) as ICommand;
if (command != null && command.CanExecute(element))
{
command.Execute(element);
e.Handled = true;
}
}
}
which then can be used in xaml as below.
<Slider Minimum="0" Maximum="100" local:Extensions.ValueChangedCommand="{Binding ValueChangedCommand}"/>
As #Philip W stated, you could use e.g. MVVMLight to help dealing with MVVM pattern and with your problem at hand.
You could, for example, have a XAML with DataTemplate and Slider like so:
<Window x:Class="WpfApplication1.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:WpfApplication1"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight"
mc:Ignorable="d"
Title="MainWindow"
Height="250"
Width="250">
<Window.Resources>
<DataTemplate x:Key="SomeTemplate">
<StackPanel Margin="15">
<!-- Wrong DataContext can drive you mad!1 -->
<StackPanel.DataContext>
<local:SomeTemplateViewModel />
</StackPanel.DataContext>
<TextBlock Text="This is some template"/>
<Slider
Height="30"
IsSnapToTickEnabled="True"
Maximum="100"
SmallChange="1"
IsMoveToPointEnabled="True">
<!-- Bind/pass event as command -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<command:EventToCommand
Command="{Binding Mode=OneWay, Path=ValueChangedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Slider>
<!-- Show current value, just for sake of it... -->
<TextBlock
Text="{Binding Value}"
FontWeight="Bold"
FontSize="24">
</TextBlock>
</StackPanel>
</DataTemplate>
</Window.Resources>
<ContentControl ContentTemplate="{StaticResource SomeTemplate}" />
</Window>
So basically you bind desired event to named Command and pass EventArgs to it as parameter. Then in your ViewModel, being the DataContext of you Slider, you handle the event-passed-as-command.
public class SomeTemplateViewModel : ViewModelBase
{
private double _value;
public SomeTemplateViewModel()
{
// Create command setting Value as Slider's NewValue
ValueChangedCommand = new RelayCommand<RoutedPropertyChangedEventArgs<double>>(
args => Value = args.NewValue);
}
public ICommand ValueChangedCommand { get; set; }
public double Value
{
get { return _value; }
set { _value = value; RaisePropertyChanged(); } // Notify UI
}
}
This would give you something similar to this.
Since your slider is dynamically generated, nothing prevents you from adding your ValueChanged event at a later time:
XAML:
<Slider x:Name="slider" HorizontalAlignment="Left" Margin="10,143,0,0" VerticalAlignment="Top" Width="474" Grid.ColumnSpan="2" />
Code-behind:
public MainWindow()
{
InitializeComponent();
// it is a good idea to not allow designer to execute custom code
if (DesignerProperties.GetIsInDesignMode(this))
return;
slider.ValueChanged += Slider_ValueChanged;
}
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// do your stuff here
}
Checking design mode is not simple in any context, as pointed out here.

WPF Context Menu, Copy Menu Item is grayed out

I have a ListView databinded to a ObservableCollection of a class. I'm trying to add a "Copy" menu item to the ListView like so:
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Command="{x:Static ApplicationCommands.Copy}"></MenuItem>
<MenuItem Command="{x:Static ApplicationCommands.Copy}"
CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"></MenuItem>
</ContextMenu>
</ListView.ContextMenu>
Now when i right click a menu item.. the menu comes up but the Copy is grayed out.. my educated guess is that it thinks there's nothing for it to copy.. but that doesn't make sense because when i right click a listbox item.. i'm technically selecting something for it to copy.. and the listbox item is selected as i'm doing this..I just want it to copy the selected text in the ListView.
What do I have to do to get this to work? Overwrite a copy class in my class that's binded to the Listview? I tried googling and not getting very far.
I've just put together an example that works for me:
<Window.CommandBindings>
<CommandBinding
Command="ApplicationCommands.Copy"
CanExecute="CommandBinding_CanExecute"
Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
<Window.Resources>
<ContextMenu x:Key="MyContextMenu">
<MenuItem Header="Copy" Command="ApplicationCommands.Copy"/>
</ContextMenu>
<Style x:Key="MyItemContainerStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource MyContextMenu}" />
</Style>
</Window.Resources>
<Grid>
<ListView x:Name="MyListView" ItemContainerStyle="{StaticResource MyItemContainerStyle}"/>
</Grid>
Then in the code behind:
// Test class with a single string property
private class MyData
{
public String Name { get; set; }
public MyData(String name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
}
public MainWindow()
{
InitializeComponent();
// Create some test data
ObservableCollection<MyData> names = new ObservableCollection<MyData>();
names.Add(new MyData("Name 1"));
names.Add(new MyData("Name 2"));
names.Add(new MyData("Name 3"));
MyListView.ItemsSource = names;
}
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
Clipboard.SetText(MyListView.SelectedItem.ToString());
}
This works whether you select Copy from the context menu, or use Ctrl+C
Without having you rewrite everything, the thing to note about Gary's sample is the presence of a CanExecute statement, which is what controls the enabling/disabling of commands. You should look in to the proper command structure some more, because I think you're missing the real power of the command.
https://msdn.microsoft.com/en-us/library/ms753200%28v=vs.110%29.aspx

Fill a Dockpanel with a WPF Webbrowser in MVVM pattern

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()

Categories