Keep a MenuItem's IsChecked property synced with a bool - c#

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.

Related

How to run a method every time a TabItem is selected, in an MVVM application using Prism

I have been trying to implement this for a while and haven't been able to do it so far, despite having the feeling that this should be something easy.
The difficulty comes from the fact that I have implemented a WPF application using the MVVM pattern. Now, this is my first attempt at both the pattern and the framework, so it is almost guaranteed that I have made mistakes while trying to follow the MVVM guidelines.
My implementation
I have three Views with their respective ViewModels (wired using Prism's AutoWireViewModel method). The MainView has a TabControl with two TabItems, each of witch contains a Frame container with the Source set to one of the other two Views. The following code is an excerpt of the MainView:
<TabControl Grid.Row="1" Grid.Column="1">
<TabItem Header="Test">
<!--TestView-->
<Frame Source="View1.xaml"/>
</TabItem>
<TabItem Header="Results">
<!--ResultsView-->
<Frame Source="View2.xaml"/>
</TabItem>
</TabControl>
My problem
Every time that someone changes to a specific TabItem, I would like to run a method that updates one of the WPF controls included in that View. The method is already implemented and bound to a Button, but ideally, no button should be necessary, I would like to have some kind of Event to make this happen.
I appreciate all the help in advance.
You could for example handle the Loaded event of the Page to either call a method or invoke a command of the view model once the view has been loaded initially:
public partial class View2 : Page
{
public View2()
{
InitializeComponent();
Loaded += View2_Loaded;
}
private void View2_Loaded(object sender, RoutedEventArgs e)
{
var viewModel = DataContext as ViewModel2;
if (viewModel != null)
viewModel.YourCommand.Execute(null);
Loaded -= View2_Loaded;
}
}
The other option would be handle this in the MainViewModel. You bind the SelectedItem property of the TabControl to a property of the MainViewModel and set this property to an instance of either ViewModel2 or ViewModel2, depending on what kind of view you want to display.
You could then call any method or invoked any command you want on these. But this is another story and then you shouldn't hardcode the TabItems in the view and use Frame elements to display Pages. Please take a look here for an example:
Selecting TabItem in TabControl from ViewModel
Okay, so What I have done is Create a Custom Tab Control. I will write out step by step instructions for this, and then you can add edit to it.
Right click on your solution select add new project
Search For Custom Control Library
High Light the name of the class that comes up, and right click rename it to what ever you want I named it MyTabControl.
Add Prism.Wpf to the new project
Add a reference to the new project to where ever your going to need it. I needed to add to just the main application, but if you have a separate project that only has views then you will need to add it to that too.
Inherit your Custom Control From TabControl Like:
public class MyTabControl : TabControl
You will notice that there is a Themes folder in the project you will need to open the Generic.xaml and edit it. it should look like:
TargetType="{x:Type local:MyTabControl}" BasedOn="{StaticResource {x:Type TabControl}}" for some reason this will not let me show the style tags but they will need to be in there as well
Please review this code I got this from Add A Command To Custom Control
public class MyTabControl : TabControl
{
static MyTabControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyTabControl), new FrameworkPropertyMetadata(typeof(MyTabControl)));
}
public static readonly DependencyProperty TabChangedCommandProperty = DependencyProperty.Register(
"TabChangedCommand", typeof(ICommand), typeof(MyTabControl),
new PropertyMetadata((ICommand)null,
new PropertyChangedCallback(CommandCallBack)));
private static void CommandCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myTabControl = (MyTabControl)d;
myTabControl.HookupCommands((ICommand) e.OldValue, (ICommand) e.NewValue);
}
private void HookupCommands(ICommand oldValue, ICommand newValue)
{
if (oldValue != null)
{
RemoveCommand(oldValue, oldValue);
}
AddCommand(oldValue, oldValue);
}
private void AddCommand(ICommand oldValue, ICommand newCommand)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
var canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
}
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (this.TabChangedCommand != null)
{
if (TabChangedCommand.CanExecute(null))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
}
private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = CanExecuteChanged;
oldCommand.CanExecuteChanged -= handler;
}
public ICommand TabChangedCommand
{
get { return (ICommand) GetValue(TabChangedCommandProperty); }
set { SetValue(TabChangedCommandProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SelectionChanged += OnSelectionChanged;
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (TabChangedCommand != null)
{
TabChangedCommand.Execute(null);
}
}
}
you will need to add the name space in your window or usercontrol like:
xmlns:wpfCustomControlLibrary1="clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1"
and here is your control:
<wpfCustomControlLibrary1:MyTabControl TabChangedCommand="{Binding TabChangedCommand}">
<TabItem Header="View A"></TabItem>
<TabItem Header="View B"></TabItem>
</wpfCustomControlLibrary1:MyTabControl>
This is how I'd approach this sort of requirement:
View:
<Window.DataContext>
<local:MainWIndowViewModel/>
</Window.DataContext>
<Grid>
<TabControl Name="tc" ItemsSource="{Binding vms}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type local:uc1vm}">
<local:UserControl1/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:uc2vm}">
<local:UserControl2/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding TabHeading}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Window>
When it has a uc1vm it will be templated into usercontrol1 in the view.
I'm binding to a collection of viewmodels which all implement an interface so I know for sure I can cast to that and call a method.
Main viewmodel for window:
private IDoSomething selectedVM;
public IDoSomething SelectedVM
{
get { return selectedVM; }
set
{
selectedVM = value;
selectedVM.doit();
RaisePropertyChanged();
}
}
public ObservableCollection<IDoSomething> vms { get; set; } = new ObservableCollection<IDoSomething>
{ new uc1vm(),
new uc2vm()
};
public MainWIndowViewModel()
{
}
When a tab is selected, the setter for selected item will be passed the new value. Cast that and call the method.
My interface is very simple, since this is just illustrative:
public interface IDoSomething
{
void doit();
}
An example viewmodel, which is again just illustrative and doesn't do much:
public class uc1vm : IDoSomething
{
public string TabHeading { get; set; } = "Uc1";
public void doit()
{
// Your code goes here
}
}
I appreciate all of your input, but I found an alternative solution. Given the information given by #mm8, I took advantage of the Loaded event but in a way that does not require any code in the code behind.
My solution
In the View which I would like to give this ability to execute a method every time the user selects the TabItem that contains it, I added the following code:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding OnLoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
And then simply implemented a DelegateCommand called OnLoadedCommand in the View's respective ViewModel. Inside that command I call my desired method.
Please comment if you spot anything wrong with this approach! I chose to try this since it required the least amount of changes to my code, but I may be missing some vital information regarding problems the solution may cause.

How to instance a child Window in WPF that takes parameters from the main Window, but respects MVVM

I have implemented something violating the MVVM pattern, and I wondered if there was a MVVM way of doing this.
I have a Window MainWindow, its DataContext is bound to a class called ViewModel which implements INotifyPropertyChanged.
I also implemented a Window ChildWindow which appears in a "Dialog" style when a button is clicked, using a RelayCommand. The DataContext of ChildWindow also binds to ViewModel. This Window is used to fill the details of a new list Item. I pass the View as a CommandParameter to the ViewModel, so that the ChildWindow can be centered in comparison to the MainWindow. This is not MVVM, and I would like to change this.
First, I implemented this in a non-MVVM way:
Here is my XAML for the button in MainWindow which opens the ChildWindow:
<Button Name="BtnInsert" Width="50" Margin="10" Command="{Binding OpenChildWindowCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">Add</Button>
Here is my simplified XAML for the ChildWindow:
<Window x:Class="HWE_Einteilen_Prototype.View.ListItemWindow"
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:HWE_Einteilen_Prototype.View"
mc:Ignorable="d"
Title="test" Height="400" Width="400">
<TextBox Width="50" Text="{Binding CurrentListItem.Id}" ></TextBox>
</Window>
And here is my (simplified) ViewModel Class:
public class ViewModel : INotifyPropertyChanged
{
private DataContext _ctx;
private ListItem _currentListItem;
private ObservableCollection<listItem> _listItems;
private ListItemWindow _listItemWindow;
private ICommand _openListItemWindowCommand;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ListItem> ListItems
{
get { return _listItems; }
set
{
_listItems = value;
OnPropertyChanged();
}
}
public ListItem CurrentListItem
{
get { return _currentListItem; }
set
{
_currentListItem = value;
OnPropertyChanged();
}
}
public ICommand OpenListItemWindowCommand
{
get { return _openListItemWindowCommand; }
set
{
_openListItemWindowCommand = value;
OnPropertyChanged();
}
}
public ViewModel()
{
OpenListItemWindowCommand = new RelayCommand(this.OpenNewListItemWindow, this.CanOpenListItemWindow);
}
private void OpenNewListItemWindow(object parameter)
{
CurrentListItem = new listItem(){Id = "testId"};
_listItemWindow = new StListItemWindow(){DataContext = this};
_listItemWindow.Owner = (MainWindow)parameter;
_listItemWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner;
_listItemWindow.Closing += OnStListItemWindowClosing;
_listItemWindow.Show();
}
private bool CanOpenListItemWindow(object parameter)
{
return true;
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
What I have tried:
I have tried implementing a Behavior (from system.windows.interactivity) for the button opening the child window, so that it creates a new Window and does all the centering and owner stuff, and leaving only CurrentListItem = new listItem(){Id = "testId"}; in the command method. However, in this case binding to CurrentListItem in the ChildWindow throws an exception.
XAML Code for the MainWindow Button:
<Button Name="BtnInsert" Width="50" Margin="10" Command="{Binding OpenListItemWindowCommand}" Content="Add">
<i:Interaction.Behaviors>
<behaviors:BehButtonNewWindow></behaviors:BehButtonNewWindow>
</i:Interaction.Behaviors>
</Button>
Behavior Code:
class BehButtonNewWindow : Behavior<Button>
{
private StListItemWindow _ListItemWindow;
protected override void OnAttached()
{
AssociatedObject.Click += OnClickHandler;
}
protected override void OnDetaching()
{
AssociatedObject.Click -= OnClickHandler;
}
private void OnClickHandler(object sender, RoutedEventArgs routedEventArgs)
{
if (sender is Button button)
{
var win = Window.GetWindow(button);
if (win != null)
{
_ListItemWindow = new ListItemWindow
{
DataContext = win.DataContext,
Owner = win,
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
_ListItemWindow.Show();
}
}
}
}
Code of Command Execute Method from ViewModel:
private void OpenNewStListItemWindow(object parameter)
{
CurrentListItem = new ListItem(){Id = "testId"};
}
What am I doing wrong?
Credit for this answer goes to Will (see comments)
On handling the window opening:
Opening a window is a UI concern. Simply handle the button click in the codebehind, construct a new window and stick the current VM in it. MVVM != no codebehind.
On handling vm code:
[...] If you mean that last little bit of code at the bottom, make it public and have the window call it before opening the new window. The UI is perfectly fine knowing about your view models. They're designed to display their state and bind to their properties.
Thanks for your help!

Access methods from the view in the viewmodel

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

Using commands to fire usercontrol method

I need some help and I hope you can help me. I have a complex usercontrol with a method that changes the color of all elements inside. When I try to connect it with a method stub in the MainWindow-Code-behind, I can fire it up easily. I want to use MVVM in the future so now I want to connect it to a button in the main window through commands.
So here's my ViewModel and my MainWindow.cs
public class ViewModel
{
public DelegateCommands TestCommand { get; private set; }
public ViewModel()
{
TestCommand = new DelegateCommands(OnExecute, CanExecute);
}
bool CanExecute(object parameter)
{
return true;
}
void OnExecute()
{
//testUC.NewColor(); HERE I WANT TO START THE UC-METHOD
}
}
public partial class MainWindow : Window
{
ViewModel _ViewModel = null;
public plate tplate;
public MainWindow()
{
InitializeComponent();
_ViewModel = new ViewModel();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
tplate = new plate();
}
}
On my MainWindow-View I have a simple button and the usercontrol.
<exte:plate x:Name="testUC" Grid.Column="1"/>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="43,247,0,0" Command="{Binding TestCommand}"/>
I want to start the UC-Method in the OnExecute()-Method but I'm not able to select the "testUC" because it's not available in this context.
Is there an easy way to start the UC-Methods through command bindings?
Thanks for any help.
Timo
How to solve you're binding problem. First, most bindings are related to the most specific DataContext. Means, you have to set your control as such. E.g.
public class MySpecialButton : Button
{
public MySpecialButton()
{
DataContext = this; // there are other possibilties, but this is the easiest one
}
}
With this you can bind every command implemented in MySpecialButton.
Another possibility is to use bindings with relative source. E.g.
<Button Command="{Binding TheCmd, RelativeSource={AncestorType={x:Type MySpecialButton}}}" />
You could even declare the DataContext with the method in the example above.
Hope this helps you.
Ok, I tried it the latter way you described ("Button" is the Button I want to use to trigger the method, "NewPlate" is the method-name and exte:plate is the customcontrol-type):
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="43,247,0,0" Command="{Binding NewPlate, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type exte:plate}}}" />
But again, nothing happens.
To understand your first suggestion correctly, I have to declare my Custom-Control as a Button instead of a user control? I do not understand, where I have to put the datacontext stuff. I'm just using a standard wpf-button to trigger the custom-control-method. I do not have any class for this button where I can declare a datacontext.

Toggle button command not executed

The problem is this. Let's say I have 3 toggle buttons and I want just one being checked at the time using Command. When one button is checked others should be disabled. (I don't want to use radio buttons).
So I created this simple code but the strange thing is, that when checked button is clicked commands Execute is not executed (no MessageBox is shown).
<Window x:Class="ToggleButtonsProblem.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">
<StackPanel>
<ToggleButton Command="{Binding ToggleCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}">A</ToggleButton>
<ToggleButton Command="{Binding ToggleCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}">B</ToggleButton>
<ToggleButton Command="{Binding ToggleCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}">C</ToggleButton>
</StackPanel>
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls.Primitives;
namespace ToggleButtonsProblem {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel {
public static ICommand ToggleCommand { get { return new ToggleCommand(); } }
}
public class ToggleCommand : ICommand {
public static bool isSomeChecked = false;
public static ToggleButton currentCheckedButton;
public bool CanExecute(object parameter) {
if (currentCheckedButton == null) return true;
return (parameter as ToggleButton).IsChecked == true;
}
public event EventHandler CanExecuteChanged {
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter) {
currentCheckedButton = null;
ToggleButton button = parameter as ToggleButton;
MessageBox.Show(button.IsChecked.ToString());
if (button.IsChecked == true) {
currentCheckedButton = button;
}
else {
currentCheckedButton = null;
}
}
}
}
Commands are executed only when button is pressed. You need to hook the Unchecked event of the ToggleButton, for example like this:
<ToggleButton Command="{Binding ToggleCommand}" Unchecked="ToggleButton_Unchecked" CommandParameter="{Binding RelativeSource={RelativeSource Self}}">A</ToggleButton>
And add method handler to the code-behind class:
public void ToggleButton_Unchecked(object sender, RoutedEventArgs e) {
(sender as ToggleButton).Command.Execute(sender);
}
This should work, perhaps you can find some prettier way of adding the method handler, maybe as a part of ToggleCommand class.
EDIT:
Try implementing your CanExecute() method like this:
public bool CanExecute(object parameter) {
if (currentCheckedButton == null) return true;
return currentCheckedButton == parameter;
}
For me it works. Here is what I think caused the problem: you click (uncheck) the button, so IsChecked changed to false. Then WPF attempts to invoke the Execute() method, but as always, calls CanExecute() first. However, CanExecute() returns false, because the check state has already been changed, so the Execute() methods is not invoked.
ToggleCommand should not be static. Try to define the command as a property.

Categories