Access methods from the view in the viewmodel - c#

I'm making a simple WPF program that reads from the clipboard. The program will have a notification icon with a context menu. And now for my question.
How can I call the methods CloseCBViewer(), InitCBViewer() from MainWindow from NotifyIconViewModel, so I can use them in ExitCommand to disconnect from clipboard and add a disconnect and connect options in a context menu.
For the system tray icons I'm using this: http://www.codeproject.com/Articles/36468/WPF-NotifyIcon
NotifyIconViewModel.cs
public class NotifyIconViewModel
{
public ICommand ExitCommand
{
get
{
return new DelegateCommand
{
CommandAction = () =>
{
Application.Current.Shutdown();
}
};
}
}
}
public class DelegateCommand : ICommand
{
public Action CommandAction { get; set; }
public Func<bool> CanExecuteFunc { get; set; }
public void Execute(object parameter)
{
CommandAction();
}
public bool CanExecute(object parameter)
{
return CanExecuteFunc == null || CanExecuteFunc();
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
MainWindow.cs
public partial class MainWindow : Window
{
public void InitCBViewer()
{
WindowInteropHelper wih = new WindowInteropHelper(this);
hWndSource = HwndSource.FromHwnd(wih.Handle);
// start processing window messages
hWndSource.AddHook(this.WinProc);
// set this window as a viewer
hWndNextViewer = Win32.SetClipboardViewer(hWndSource.Handle);
}
enter code here
public void CloseCBViewer()
{
// remove this window from the clipboard viewer chain
Win32.ChangeClipboardChain(hWndSource.Handle, hWndNextViewer);
hWndNextViewer = IntPtr.Zero;
hWndSource.RemoveHook(this.WinProc);
}
}
ClipboardResources.xaml
<ResourceDictionary 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"
xmlns:local="clr-namespace:ClipboardTextChecker">
enter code here
<ContextMenu x:Shared="false" x:Key="SysTrayMenu">
<MenuItem Header="Show" Command="{Binding ShowWindowCommand}" />
<MenuItem Header="Hide" Command="{Binding HideWindowCommand}" />
<Separator/>
<MenuItem Header="Exit" Command="{Binding ExitCommand}" />
</ContextMenu>
<tb:TaskbarIcon x:Key="NotifyIcon"
ToolTipText="Double-click for show checker, right-click for menu"
DoubleClickCommand="{Binding ShowWindowCommand}"
ContextMenu="{StaticResource SysTrayMenu}">
<tb:TaskbarIcon.DataContext>
<local:NotifyIconViewModel/>
</tb:TaskbarIcon.DataContext>
</tb:TaskbarIcon>
</ResourceDictionary>
App.xaml
<Application x:Class="ClipboardTextChecker.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClipboardTextChecker"
ShutdownMode="OnExplicitShutdown">
<Application.Resources>
enter code here
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ClipboardTextCheckerResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

Before I answer, just want to let you know that this breaks the rules of MVVM:
public ICommand ExitCommand
{
get
{
return new DelegateCommand
{
CommandAction = () =>
{
Application.Current.Shutdown(); // <-- Don't do this
}
};
}
}
}
Your view-models are not supposed to know anything about the UI. By accessing the Application directly, you're breaking the rules. I will leave that up to you to discover later.
To answer your question
Your application is the Application.Current. Assuming it's the default name, you can cast it like this:
var myApp = (App)Application.Current;
In your App.xaml.cs, you can create a public method that does everything for you.
public partial class App : Application
{
public void CustomShutdown()
{
// Not 100% sure if this is how you get access to your main window.
var main = (MainWindow)Window.Current;
main.CloseCBViewer(); // This is your custom shutdown.
Shutdown(); // This is equivalent to Application.Current.Shutdown()
}
}
And in your original ExitCommand:
public ICommand ExitCommand
{
get
{
return new DelegateCommand
{
CommandAction = () =>
{
((App)Application.Current).CustomShutdown();
}
};
}
}
}

So, the MVVM way of doing this is to make sure that your view-model doesn't have links to your view. The appropriate way to do this might be to introduce an Exit event in your VM, then when you create your VM in the MainWindow you attach an handler to that event, which then detaches your clipboard.

I would recommend that you use a decoupled publish-subscribe event methodology. There is one called the EventAggregator that ships with Microsoft's Prism.
Note that recently Microsoft open sourced Prism - don't be concerned about that specifically, the Event Aggregator pattern (and the EventAggregator library) is still the appropriate pattern to use here. This will enable you to define events, then separately and independantly register subscribers (listeners) for specific published events (the event aggregator acts as a broker and register for subscribers).

Related

How to implement event EventHandler ICommand.CanExecuteChanged.add? (C#)

I was following this video on how to create a Windows.InputBinding for the Exit MenuItem:
https://www.youtube.com/watch?v=bdmVWGjpA_8
Here's my attempt, But I'm wondering how to implement:
event EventHandler ICommand.CanExecuteChanged add { }???
Seems this CanExecureChanged is a new requirement for ICommand Interface in DotNet 6 WPF??
Heres's part of my c# MainWindow.cs:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApp2
{
public class ExitKey : ICommand
{
private MainWindow mw;
public ExitKey(MainWindow mw0) { mw = mw0;}
event EventHandler ICommand.CanExecuteChanged {
add {
//How to Implment this???
throw new NotImplementedException();
}
remove {
throw new NotImplementedException();
}
}
bool ICommand.CanExecute(object parameter) {
return true;
}
void ICommand.Execute(object parameter) {
mw.menu1_file_exit.RaiseEvent(new
RoutedEventArgs(MenuItem.ClickEvent));
}
}
public class KeyContext
{
private MainWindow mw;
public KeyContext(MainWindow mw0) { mw = mw0; }
// {Binding MyExitCommand}
public ICommand MyExitCommand
{
get { return new ExitKey(mw); }
}
} //Class
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new KeyContext(this);
}
private void menu1_file_exit_Click(
object sender, RoutedEventArgs e)
{
MessageBox.Show("EXIT!");
}
} //Class
} //Namespace
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Garbo" Height="900" Width="900" MinHeight="240" MinWidth="320">
<Window.InputBindings>
<KeyBinding Key="F5"
Command="{Binding MyExitCommand}"
/>
</Window.InputBindings>
<DockPanel>
<Menu Name="menu1" DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem
Name="menu1_file_exit"
Header="E_xit"
Command="{Binding ExitCommand}"
InputGestureText="F5"
Click="menu1_file_exit_Click"/>
</MenuItem>
</Menu>
</DockPanel>
</Window>
You implement it by forwarding the calls to your own event handler.
However you don't really need to implement your own commands, there are plenty already done out there. The MVVM Community Toolkit has its RelayCommand and the one I use is ReactiveUI's ReactiveCommand.
In WPF you would usually delegate event subscribers to the CommandManager.RequerySuggested event:
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
This way the CommandManager will be responsible to raise the ICommand.CanExecuteChanged event implicitly.
The CommandManager would observe the UI interaction like mouse move and focus changes and will raise the CommandManager.RequerySuggested event to notify the ICommandSource, that usually listens to the ICommand.CanExecuteChanged event. The command source will then call ICommand.CanExecute.
You can trigger the CommandManager.RequerySuggested event (and therefore the ICommand.CanExecuteChanged event) explicitly by calling CommandManager.InvalidateRequerySuggested.
An alternative implementation is to bypass the CommandManager and allow the command target to raise this event explicitly:
public
public event EventHandler CanExecuteChanged;
public void InvalidateCommand() => this.CanExecuteChanged?.Invoke(this, EventArgs.Empty);
You can follow the Microsoft Docs: Relaying Command Logic example on how to implement a reusable RelayCommand. This example generally shows how to implement ICommand properly.
Note that in your case the ICommand ExitKey should be a RoutedCommand: How to: Create a RoutedCommand.
RoutedCommand also allows to register a KeyGesture with the command:
partial class MainWindow : Window
{
public static RoutedCommand MyExitCommand = new RoutedCommand();
public MainWindow()
{
InitializeComponent();
var exitCommandKeyGesture = new KeyGesture(Key.F5);
MyExitCommand.InputGestures.Add(exitCommandKeyGesture);
var commandBinding = new CommandBinding(MyExitCommand, ExecutedMyExitCommand, CanExecuteMyExitCommand);
this.CommandBindings.Add(commandBinding);
}
}
<Window>
<DockPanel>
<Menu Name="menu1" DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem x:Name="menu1_file_exit"
Header="E_xit"
Command="{x:Static local:MyExitCommand}"
InputGestureText="F5" />
</MenuItem>
</Menu>
</DockPanel>
</Window>

WPF set ViewModel of View from DataTemplate

I have been attempting to implement a Sudoku game within a WPF application I am making. I found the following site that pretty much gave me the perfect starting point to try and add the sudoku to my app.
There is however a major difference which I didn't think much of at first. The code from this site bases everything off a single Window, no UserControls at all, which in itself isn't an issue. However, my current implementation bases the whole content of the app on a ContentControl element.
To skip useless details, here is my MainWindow.xaml file (with everything unrelated to the issue removed):
<Window x:Class="BasicGameApp.MainWindow.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:BasicGameApp.MainWindow"
xmlns:viewModel="clr-namespace:BasicGameApp.MainWindow.MVVM.ViewModel"
mc:Ignorable="d"
Height="700"
Width="1080"
WindowStartupLocation="CenterScreen"
WindowStyle="None"
ResizeMode="NoResize"
Background="Transparent"
AllowsTransparency="True">
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
<Border>
<Grid>
<ContentControl Grid.Row="1"
Grid.Column="1"
Margin="10"
Content="{Binding CurrentView}"/>
</Grid>
</Border>
</Window>
I based my UI on this YouTube tutorial if anyone is curious.
The MainViewModel.cs looks like this:
namespace BasicGameApp.MainWindow.MVVM.ViewModel
{
class MainViewModel : ObservableObject
{
#region Commands
public RelayCommand HomeViewCommand { get; set; }
public RelayCommand SudokuViewCommand { get; set; }
#endregion
#region ViewModels
public HomeViewModel HomeVM { get; set; }
public SudokuViewModel SudokuVM { get; set; }
#endregion
private object _currentView;
public object CurrentView
{
get => _currentView;
set
{
_currentView = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
HomeVM = new HomeViewModel();
SudokuVM = SudokuViewModel.GetInstance(new SudokuView());
HomeViewCommand = new RelayCommand(o =>
{
CurrentView = HomeVM;
});
SudokuViewCommand = new RelayCommand(o =>
{
CurrentView = SudokuVM;
});
}
}
}
And here are the ObservableObject RelayCommand classes:
namespace BasicGameApp.MainWindow.Core
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class RelayCommand : ICommand
{
private Action<object> _execute;
private Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
}
As you can see, everything is programmed correctly and works as intended EXCEPT, the SudokuViewModel. As you can see from the code, the SudokuViewModel isn't instantiated as a public class (this is from the tutorial I stated at the beginning). From that tutorial, the instantiation of the view and associated viewmodel is done as follows in the App.xaml.cs:
public partial class App : Application
{
public void ApplicationStartup(object sender, StartupEventArgs args)
{
MainWindow mainWindow = new MainWindow(); // Instantiate the main window
mainWindow.ViewModel = ViewModelClass.GetInstance(mainWindow); // Get an instance of the ViewModel and set the View's ViewModel pointer
mainWindow.Show(); // Now display the view
}
}
My App.xaml file isn't empty however and contains the following:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/BasicGameApp.MainWindow;component/Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type viewModel:HomeViewModel}">
<view:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:SudokuViewModel}">
<view:SudokuView/>
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
Needless to say that the difference between these two setups makes things rather difficult. I attempted to change the DataTemplate to set the ViewModel property on the SudokuView but I can't seem to get it to Bind correctly:
<view:SudokuView ViewmModel="{Binding //The calling ViewModel}"/>
I'm currently out of ideas as to how I can either adapt the sudoku code to work "without" a viewmodel or adapt the DataTemplate to provide the view with the SudokuViewModel.
Please bare in mind that I am initially an Android developper and this is simply to learn new skills on a personal level.

How to set a click event on DataContext (INotifyPropertyChanged) property (WPF - C# - XAML)?

What I'm trying to accomplish
I'm trying to have my nested menu item change the shown user control. In more technical terms, I'm trying to:
Get a click event attached to a nested MenuItem (from my MyMenu.cs file - implements INotifyPropertyChanged), to...
Use RoutedEventHandler (maybe from the MyMenu.cs file? - implements UserControl), to...
Call the SwitchScreen method (from my MainWindow.cs file - implements Window)
Where I'm getting stuck
I can't seem to find a way to add the click event to the appropriate menu item.
My current logic also requires the original sender to be passed as an argument so that I can identify the correct MySubview to display.
XAML Handler
I've tried adding the click event in xaml as follows, but it only adds the handler to the first menu item level (not to nested menu item elements).
<MenuItem ItemsSource="{Binding Reports, Mode=OneWay}" Header="Reports">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<EventSetter Event="Click" Handler="MenuItem_Click"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
C# Setter
I've tried adding a setter, suggested in this answer, but I can't seem to create a click event from MyMenu.cs to MyMenuUserControl.cs.
Style style = new Style();
style.BasedOn = menuItem2.Style;
style.Setters.Add(new EventSetter( /* ??? */ ));
C# ICommand
I've tried using ICommand, suggested in this answer, but I can't seem to create a relay command from MyMenu.cs to MyMenuUserControl.cs.
I may be doing something wrong in one of these attempts, but I'm now past the point of playing around and ready to throw in the towel.
Notes
Actual structure
In reality, my actual code has n-nested foreach loops to generate the menu and I remove a level of nesting if a the foreach enumerable (e.g. myObjects) only has one element.
The removal of a level of nesting also moves the click event up one level.
My final menu could look something like this:
My menu items:
Item (menuItem1)
Item (menuItem2)
Item (menuItem3) + click event
Item (menuItem3) + click event
Item (menuItem2) + click event (see A)
Item (menuItem1) + click event (see B)
A: Only one menuItem3 is nested, so we remove it (it's redundant) and we move the click event up to menuItem2.
B: Only one menuItem2 is nested, and it only has one menuItem3. Both are removed as they're redundant and we move the click event is moved to menuItem1.
This is why I'd like to maintain the creation of the menu items in the MyMenu class.
Other suggestions
I could be going about this completely wrong and I'm open to suggestions that change the way I'm going about this.
Code
MyMenu.cs
The constructor in this class generates my menu items and its sub-menu items.
This is where I'm trying to add a click event.
class MyMenu : INotifyPropertyChanged
{
private List<MenuItem> menuItems = new List<MenuItem>();
public List<MenuItem> MenuItems
{
get { return menuItem; }
set
{
menuItem = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public List<Tuple<MyObject, MenuItem>> Map { get; private set; } = new List<Tuple<MyObject, MenuItem>>();
public MyMenu(List<MyObject> myObjects)
{
foreach(MyObject myObject in myObjects)
{
MenuItem menuItem1 = new MenuItem { Header = myObject.Name };
foreach(string s in myObject.Items)
{
MenuItem menuItem2 = new MenuItem { Header = s };
// Add click event to menuItem2 here
menuItem1.Items.Add(menuItem2);
Map.Add(new Tuple<MyObject, MenuItem>(myObject, menuItem2));
}
MenuItem.Add(menuItem1);
}
}
private void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
MyMenuUserControl.xaml
Minimal code sample UserControl (uses default xmlns attributes).
MyMenuUserControl.xaml.cs only has constructor with InitializeComponent();
<UserControl>
<!-- xmlns default attributes in UserControl above removed for minimal code -->
<Menu>
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<MenuItem ItemsSource="{Binding MenuItems, Mode=OneWay}" Header="My menu items"/>
</Menu>
</UserControl>
MyDataContext.cs
Minimal code sample (same PropertyChangedEventHandler and OnPropertyChanged() code as MyMenu.cs).
Constructor simply sets Menu and Subviews properties.
class MyDataContext : INotifyPropertyChanged
{
private MyMenu menu;
public MyMenu Menu
{
get { return menu; }
set
{
menu = value;
OnPropertyChanged();
}
}
private List<MySubview> mySubviews;
public List<MySubview> MySubviews
{
get { return mySubviews; }
set
{
mySubviews = value;
OnPropertyChanged();
}
}
// ... rest of code removed to maintain minimal code
}
MainWindow.xaml.cs
Subview contains a property of MyObject type.
This allows me to use MyMenu's Map property to identify which Subview to display for a given MenuItem's click.
Yes, making the map at the MainWindow map might be easier, however the logic I have in MyMenu is a minimal example (see Notes for more info).
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// I get my data here
List<MyObject> myObjects = ...
List<MySubview> mySubviews = ...
DataContext = new MyDataContext(new MyMenu(myObjects), new MySubviews(mySubviews));
}
private void SwitchScreen(object sender, RoutedEventArgs e)
{
MyDataContext c = (MyDataContext)DataContext;
MyObject myObject = c.MyMenu.Map.Where(x => x.Item2.Equals(sender as MenuItem)).Select(x => x.Item1).First();
MySubview shownSubview = c.MySubviews.Where(x => x.MyObject.Equals(myObject)).First();
c.MySubviews.ForEach(x => x.Visibility = Visibility.Collapsed);
shownSubview.Visibility = Visibility.Visible;
}
}
Wpf is designed to be used via the MVVM pattern. You appear to be trying to manipulate the visual tree directly, which is probably where a lot of your problems are coming from since you appear to be half way between worlds.
What is MyMenu.cs? It looks like a view model but it contains visual items (MenuItem). VMs should not contain any visual classes. They are a data abstraction of the view.
It looks like your MyMenuVM.cs should just expose your List <MyObject>, and your view menu should bind to that. MenuItem already has a built in ICommand (after all menus are made for clicking), so you don't need to add your own click handlers. Instead you bind MenuItem.Command to a command in your VM, and possibly bind CommandParameter to supply which MyObject is firing the command.
In short, I would read up a bit about MVVM because it will make your code far cleaner and easier to understand and hopefully prevent these kind of issues.
Menu can build its items from ItemsSource using any IEnumerable object. One thing you should do - set a DataTemplate for mapping MenuItem's properties to your VM properties.
I collected some links for you that may be useful in understanding how it may be done with MVVM:
RelayCommand class - refer to Relaying Command Logic section. From my (WPF newbie's) perspective it's the best way of using Commands.
HierarchicalDataTemplate - same as DataTemplate but with ItemsSource.
Trick with Separators - may help with making Menu containing not only MenuItems in its ItemsSource (tested!)
ObservableCollection - use it instead of List for UI purpose. It fires CollectionChanged event inside when you dinamically add or remove items. And Control with ItemsSource updates its layout immediately, out-of-the-box.
Why not just a collection of Controls?
Because you may break you application causing an Exception while trying to interact with UI Elements from different Thread. Yes, you may use Dispatcher.Invoke for fix but there's a better way avoiding it: simply use Binding. Thus, you may forget about Dispatcher.Invoke-everywhere problem.
Simple Example
Using single RelayCommand for all MenuItem instances.
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
Title="MainWindow" Height="300" Width="400">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<local:MenuItemContainerTemplateSelector x:Key="MenuItemContainerTemplateSelector"/>
<Style x:Key="SeparatorStyle" TargetType="{x:Type Separator}" BasedOn="{StaticResource ResourceKey={x:Static MenuItem.SeparatorStyleKey}}"/>
<Style x:Key="MenuItemStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Command" Value="{Binding DataContext.MenuCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
<Setter Property="CommandParameter" Value="{Binding CommandName}"/>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Menu Grid.Row="0" >
<MenuItem Header="Menu" ItemsSource="{Binding MenuItems}" UsesItemContainerTemplate="True" ItemContainerTemplateSelector="{StaticResource MenuItemContainerTemplateSelector}">
<MenuItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyMenuItem}" ItemsSource="{Binding Items}" >
<MenuItem Style="{StaticResource MenuItemStyle}" UsesItemContainerTemplate="True" ItemContainerTemplateSelector="{StaticResource MenuItemContainerTemplateSelector}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:MySeparator}">
<Separator Style="{StaticResource SeparatorStyle}"/>
</DataTemplate>
</MenuItem.Resources>
</MenuItem>
</Menu>
</Grid>
</Window>
RelayCommand.cs
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
MenuItemContainerTemplateSelector.cs
public class MenuItemContainerTemplateSelector : ItemContainerTemplateSelector
{
public override DataTemplate SelectTemplate(object item, ItemsControl parentItemsControl) =>
(DataTemplate)parentItemsControl.FindResource(new DataTemplateKey(item.GetType()));
}
MenuItemViewModel.cs
public interface IMyMenuItem
{
}
public class MySeparator : IMyMenuItem
{
}
public class MyMenuItem : IMyMenuItem, INotifyPropertyChanged
{
private string _commandName;
private string _header;
private ObservableCollection<IMyMenuItem> _items;
public string Header
{
get => _header;
set
{
_header = value;
OnPropertyChanged();
}
}
public string CommandName
{
get => _commandName;
set
{
_commandName = value;
OnPropertyChanged();
}
}
public ObservableCollection<IMyMenuItem> Items
{
get => _items ?? (_items = new ObservableCollection<IMyMenuItem>());
set
{
_items = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
private ObservableCollection<IMyMenuItem> _menuItems;
private ICommand _menuCommand;
public ObservableCollection<IMyMenuItem> MenuItems
{
get => _menuItems ?? (_menuItems = new ObservableCollection<IMyMenuItem>());
set
{
_menuItems = value;
OnPropertyChanged();
}
}
public ICommand MenuCommand => _menuCommand ?? (_menuCommand = new RelayCommand(param =>
{
if (param is string commandName)
{
switch (commandName)
{
case "Exit":
Application.Current.MainWindow.Close();
break;
default:
MessageBox.Show("Command name: " + commandName, "Command executed!");
break;
}
}
}, param =>
{
return true; // try return here false and check what will happen
}));
public MainViewModel()
{
MenuItems.Add(new MyMenuItem() { Header = "MenuItem1", CommandName = "Command1" });
MenuItems.Add(new MyMenuItem() { Header = "MenuItem2", CommandName = "Command2" });
MyMenuItem m = new MyMenuItem() { Header = "MenuItem3" };
MenuItems.Add(m);
m.Items.Add(new MyMenuItem() { Header = "SubMenuItem1", CommandName = "SubCommand1" });
m.Items.Add(new MySeparator());
m.Items.Add(new MyMenuItem() { Header = "SubMenuItem2", CommandName = "SubCommand2" });
m.Items.Add(new MyMenuItem() { Header = "SubMenuItem3", CommandName = "SubCommand3" });
MenuItems.Add(new MySeparator());
MenuItems.Add(new MyMenuItem() { Header = "Exit", CommandName = "Exit" });
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Keep a MenuItem's IsChecked property synced with a bool

I have a simple WPF Application that has a menu on top. I want to add an option to make the main window to stay on top of other windows.
I created a bool named setTopMost in Property > Settings tab for users to save this setting. So, the setting will be remembered even after the app is terminated.
Everything is working as intended, I can click on the option or use the shortcut of Ctrl+T to make the window to stay on top, but I cannot get a check mark to appear next to the option when the window is on top of other windows.
I've read several articles regarding binding IsChecked to a bool, but I could not solve this problem on my own.
Here are my codes.
MainWindow.xaml
<Window.InputBindings>
<KeyBinding Gesture="Ctrl+T" Command="{Binding TopMostCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Window.InputBindings>
<MenuItem Header="_Options">
<MenuItem x:Name="Menu_AlwaysOnTop" Header="Always On _Top" IsCheckable="True" IsChecked="{Binding isTopMost}" Command="{Binding TopMostCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}" InputGestureText="Ctrl+T" />
</MenuItem>
MainWindow.xaml.cs
namespace WPF_Practice
{
public partial class MainWindow : Window
{
public bool isTopMost;
public MainWindow()
{
InitializeComponent();
DataContext = new PracticeDataContext();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
isTopMost = Properties.Settings.Default.setTopMost;
Topmost = Properties.Settings.Default.setTopMost;
}
}
public class PracticeDataContext
{
public ICommand TopMostCommand { get; } = new TopMostCommand();
}
public class TopMostCommand : ICommand
{
public void Execute(object parameter)
{
var TopMostClass = new MainWindow();
TopMostClass.WindowTopMost();
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
}
Please mind that I am doing this as a hobby and quite new to this.
The main reason why your MenuItem isn't updating properly is because you set the DataContext of the Window to PracticeDataContext.
public MainWindow()
{
InitializeComponent();
DataContext = new PracticeDataContext(); <--
}
This means that your bindings in MainWindow.xaml are going to be looking for properties in PracticeDataContext.
In this case you would want to have an IsTopMost property in your PracticeDataContext class in order for the binding to work.
Since IsTopMost isn't set until the Loaded event handler fires, you should implement INotifyPropertyChanged in your PracticeDataContext class so that your IsTopMost binding will get notified when it is set from settings.
A quick search on INotifyPropertyChanged will show you lots of examples. It's pretty easy.

Event handling in Windows Store Apps using MVVM

I am developing a Windows Store App using the MVVM pattern (no framework, just raw MVVM).
I have a user control DropboxFileplanUserControl.xaml which has an associated view model DropboxFileplanViewModel.cs. DropboxFileplanUserControl is embedded in MainPage.xaml and MainPage.xaml has an associated view model, MainPageViewModel.cs.
My question is how can I define and raise an event in DropboxFileplanViewModel.cs and handle it in MainPageViewModel.cs? Assume the event to be raised is called ImageLoaded.
EDIT: I have added the following specific code snippets...
DropboxFileplanUserControl.xaml
<UserControl
x:Class="PhotoBox.Controls.DropboxFileplanUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PhotoBox.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="using:PhotoBox.ViewModels"
xmlns:triggers="using:WinRT.Triggers"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="200">
<UserControl.DataContext>
<viewModels:DropboxFileplanViewModel />
</UserControl.DataContext>
<Grid>
<ListBox>
<!--
...
...
Here I define a ListBox and use interaction triggers to bind the SelectionChanged event to FileplanSelectionChangedCommand on the ViewModel -->
<triggers:Interactions.Triggers>
<triggers:EventTrigger EventName="SelectionChanged">
<triggers:InvokeCommandAction Command="{Binding FileplanSelectionChangedCommand}" PassEventArgsToCommand="True" />
</triggers:EventTrigger>
</triggers:Interactions.Triggers>
</ListBox>
</Grid>
DropboxFileplanViewModel.xaml Note: I've stripped out a lot of code from this snippet
public class DropboxFileplanViewModel : ViewModel
{
public DelegateCommand FileplanSelectionChangedCommand { get; set; }
public DropboxFileplanViewModel()
{
FileplanSelectionChangedCommand = new DelegateCommand(FileplanSelectionChanged);
}
private void FileplanSelectionChanged(object parameter)
{
var args = (SelectionChangedEventArgs) parameter;
// Some other stuff is done here but ultimately,
// GetImageFile is called
}
private async void GetImageFile(MetaData file)
{
// Stuff is done here to get the image
// ******************************
// Here I want to raise the event
// ******************************
}
}
DropboxFileplanUserControl is embedded in MainPage.xaml as follows...
MainPage.xaml
<controls:DropboxFileplanUserControl
Grid.Row="0"
DataContext="{Binding FileplanControl}"
Visibility="{Binding IsOpen, Converter={StaticResource BooleanToVisibilityConverter}}"
IsEnabled="{Binding IsOpen}"
<!-- *** Here I need to access the ImageLoaded event and bind it to a command in MainPageViewModel.cs *** -->
/>
So, to summarise, I need to declare and raise an event in DropboxFileplanViewModel.cs and access this event in MainPage.xaml so I can then handle it in MainPageViewModel.cs. I know how to bind the event in MainPage.xaml to a command in MainPageViewModel, I just need to know how to do the first bit, i.e. declaring and raising an event in DropboxFileplanViewModel.cs and accessing it in MainPage.xaml.
In XAML:
<Image Loaded="ImageLoaded" ... />
In xaml.cs:
public MainPageViewModel ViewModel
{
get
{
return this.DataContext as MainPageViewModel;
}
}
public void ImageLoaded( object sender, RoutedEventArgs args )
{
// call down to your view model
if( ViewModel != null )
{
ViewModel.ImageLoadedHandler( );
}
}
In response to your comment, the idea is the same for the custom UserControl. I have (what I think is) and interesting solution I don't often see others implement. It's the idea that each ViewModel has an associated View (I call it owner) and a logical parent. Similar to the visual tree XAML/WinRT constructs that allows for traversal of UI elements, the parent/owner relationship we can create in our ViewModels allow this same style of traversal in our back-end code. Consider the following:
Assume we have a custom UserControl called MyUserControl that resides in the namespace MyProject.
In MainPageView.xaml:
<Page xmlns:local="MyProject"> // or whatever your fancy-pants namespace scheme is
<Grid>
<local:MyUserControl DataContext="{Binding InnerVM}" />
</Grid>
</Page>
In you MainPageView.xaml.cs
public MainPageViewModel ViewModel
{
get
{
return this.DataContext as MainPageViewModel;
}
}
public MainPageView()
{
InitializeComponent( );
DataContext = new MainPageViewModel( null, this );
}
We're getting there. Now let's look at MainPageViewModel.cs
public MainPageViewModel : ViewModelBase // I'll explain ViewModelBase momentarily
{
public MyUserControlViewModel InnerVM { get; set; } // should be a notifying property
public MainPageViewModel( ViewModelBase parent, FrameworkElement owner )
: base( parent, owner )
{
}
}
For all intents and purposes, MyUserControlViewModel.cs is the same.
Here is ViewModelBase.cs (with some abridgments):
public ViewModelBase
{
public ViewModelBase Parent { get; set; } // should be a notifying property
public FrameworkElement Owner { get; set; } // should be a notifying property
public ViewModelBase( ViewModelBase parent, FrameworkElement owner )
{
Parent = parent;
Owner = owner;
}
}
Simple! Right? Now what does that actually do for us? Let's see. Consider the following:
In MyUserControl.xaml:
<Image Loaded="ImageLoaded" ... />
In MyUserControl.xaml.cs:
public MyUserControlVieWModel ViewModel
{
get
{
return this.DataContext as MyUserControlVieWModel;
}
}
public void ImageLoaded( object sender, RoutedEventArgs args )
{
// call down to your view model
if( ViewModel != null )
{
ViewModel.ImageLoadedHandler( );
}
}
MyUserControlViewModel.cs
Now you have two options here (and I just realized I may be over-explaining the issue for you, and I apologize. Please heavily consider option 1 for your question):
1- Use Events!
public event EventHandler ImageLoaded = delegate { };
public void OnImageLoaded( )
{
ImageLoaded( );
}
Then in MainPageViewModel.cs
public void OnImageLoaded( )
{
// handle the image loading
}
And now maybe put this in your constructor:
...
InnerVM.ImageLoaded += OnImageLoaded;
...
Now when the event is fired from within MyUserControl, MainPageViewModel will be able to respond.
The second option requires more explination, and I have to run for now. But hopefully this gets you going. Sorry for the short ending. Please respond with questions if you need to. Good luck!

Categories