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.
Related
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.
I want a user control to gets visible whenever menu is selected.
Whenever user clicks the vision menu, the AV_Credentials user control is shown. But i am unable to do datacontext for the new user control.
Menu.xaml
<UserControl x:Class="Connector.Views.Menu"
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:local="clr-namespace:Connector.Views"
xmlns:menuViewModel="clr-namespace:Connector.ViewModel.Menu"
xmlns:ViewModel="clr-namespace:Connector.ViewModel.AV_Credentials"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="350">
<UserControl.DataContext>
<menuViewModel:Menu_ViewModel/>
</UserControl.DataContext>
<Grid>
<StackPanel>
<Menu HorizontalAlignment="Stretch" VerticalAlignment="Top" >
<MenuItem Header="Credentials">
<MenuItem Header="vision" Command="{Binding Vision}"/>
<MenuItem Header="NOP" Command="{Binding NOP}"/>
</MenuItem>
<MenuItem Header="Sync"/>
</Menu>
<local:AV_Credentials Visibility="{Binding Path=AVCred}" DataContext="{Binding AV_Context}"/>
</StackPanel>
</Grid>
Menu_ViewModel.cs
class Menu_ViewModel : INotifyPropertyChanged
{
private AV_Credentials_ViewModel _av_Context;
public AV_Credentials_ViewModel AV_Context
{
get
{
if(_av_Context == null)
{
_av_Context = new AV_Credentials_ViewModel();
}
return _av_Context;
}
}
private Visibility _cred = Visibility.Hidden;
public Visibility Cred
{
get
{
return _cred;
}
set
{
_cred = value; OnPropertyChanged("Cred");
}
}
private ICommand mUpdater;
public ICommand vision
{
get
{
if (mUpdater == null)
mUpdater = new Updater(this);
return mUpdater;
}
set
{
mUpdater = value;
}
}
private class Updater : ICommand
{
private Menu_ViewModel obj;
public Updater(Menu_ViewModel _obj)
{
obj = _obj;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
obj.ShowAVCred();
}
#endregion
}
public void ShowAVCred()
{
Cred = Visibility.Visible;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
What i am trying t achieve is -
The menu will be blank.
When the user selects vision from the menu, the usercontrol will be shown in the menu.
There user will provide some data.
Whats Wrong
The visibility part is working fine, but the buttons and textboxes in the usercontrol is not working. If i assign datacontext, the button and textboxes are working, but the visibility is not working.
If required i can share the AV_Credential .xaml and .cs
Sorry for bad english
If i assign datacontext, the button and textboxes are working, but the visibility is not working.
Add "AV_Context." to the binding paths in AV_Credentials.xaml, e.g.:
<TextText Text="{Binding AV_Context.YourProperty}" />
This should work since the DataContext of the AV_Credentials control is a Menu_ViewModel and this type has a AV_Context property.
You should not explicitly set the DataContext of the UserControl because then the binding to the Menu_ViewModel's Cred property will fail as you have already discovered.
The other option would be to specify an explicit source for the Visibility binding:
<local:AV_Credentials Visibility="{Binding Path=DataContext.Cred, RelativeSource={RelativeSource AncestorType=UserControl}}"
DataContext="{Binding AV_Context}"/>
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!
I'm looking for possibility to block switching RadioButton's, but still catch Click event. Unfortunatelly using Enabled=false or IsHitTestVisible=false properties prevent Click event.
What I want to achieve is:
1. User clicks RadioButton.
2. From Click event some method is called with handler passed as argument but active RadioButton is yet unchanged.
3. When handler is called, depending on the result I want to switch RadioButton or not.
I created for you simple example.
Do not forget take from NuGet the Prism package.
I create three RadioButton's and set for they Func<bool> from some ViewModel. After PreviewMouseDown event firing, I invoke current delegate, which is Func<bool> from Tag property.
ViewModel:
namespace PostponeRadioButtonChange.Model
{
using System;
using System.Collections.Generic;
using Microsoft.Practices.Prism.Mvvm;
public class MainWindow : BindableBase
{
private List<Func<bool>> rbHandlers;
private string comment;
public List<Func<bool>> RbHandlers
{
get { return this.rbHandlers; }
private set { this.SetProperty(ref this.rbHandlers, value); }
}
public string Comment
{
get { return this.comment; }
set { this.SetProperty(ref this.comment, value); }
}
public MainWindow()
{
this.RbHandlers = new List<Func<bool>>
{
() =>
{
this.Comment = "First RadioButton clicked";
return false; // Here must be your condition for checking
},
() =>
{
this.Comment = "Second RadioButton clicked";
return false;
},
() =>
{
this.Comment = "Third RadioButton clicked";
return true; // For example, third not checked after click
}
};
}
}
}
Content of View(designer);
<StackPanel>
<TextBox Text="{Binding Path=Comment, Mode=OneWay}"/>
<RadioButton Content="First"
PreviewMouseDown="RadioButtonMouseDown"
Tag="{Binding Path=RbHandlers[0], Mode=OneTime}"/>
<RadioButton Content="Second"
PreviewMouseDown="RadioButtonMouseDown"
Tag="{Binding Path=RbHandlers[1], Mode=OneTime}"/>
<RadioButton Content="Third"
PreviewMouseDown="RadioButtonMouseDown"
Tag="{Binding Path=RbHandlers[2], Mode=OneTime}"/>
</StackPanel>
View(code-behind):
namespace PostponeRadioButtonChange
{
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using VM = PostponeRadioButtonChange.Model;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new VM.MainWindow();
}
private void RadioButtonMouseDown(object sender, MouseButtonEventArgs e)
{
var rb = (sender as RadioButton);
if (rb == null)
throw new InvalidCastException("RadioButtonMouseDown only for RadioButton's");
e.Handled = (rb.Tag as Func<bool>)?.Invoke() ?? false;
}
}
}
It is not good for the final solution, but as an example should help you. You can also create a Command in VM instead of an event handler.
I hope, it will help you)
You should handle MouseDown event on radio button and then it would prevent from tunneling down to set the radio button as checked.
static void OnMouseDown(object sender, MouseButtonEventArgs e)
{
// if some logic...
e.Handled = true;
}
Using bindings you could put the call inside the setter, like this:
xaml
<RadioButton Content="radiobutton" IsChecked="{Binding TestRadio, Mode=TwoWay}"/>
code
private bool _testRadio;
public bool TestRadio
{
get { return _testRadio; }
set { value = testradiohandler(); SetProperty(ref _testRadio, value); }
}
private bool testradiohandler()
{
return new Random().NextDouble() >= 0.5;
}
I would like to use ICommand to change the Paddle1.Y int value of my ViewModel. Am I supposed to create a class implementing ICommand interface? I have done that. But since it is a class, it doesn't have access to my ViewModel's Paddle1 property without creating a property for it. I would prefer to create the command within my ViewModel for this reason. At this point I'm trying to pass the Paddle1 to the Command as a CommandParameter in XAML. I am failing at this, and I'm not sure it is the cleanest approach to editing the state of my ViewModel either.
Could I get a code example of my UpKeyPressed command being bound to either a button or the keyboard up key? With no CommandParameter would be more clean, if the command could access my ViewModel Paddle1 property.
My ViewModel:
namespace Pong.Core.ViewModels
{
public class GamePlayViewModel
{
private readonly Paddle Paddle1;
private Paddle Paddle2;
public GamePlayViewModel()
{
Paddle1 = new Paddle();
Paddle2 = new Paddle();
UpKeyPressed();
}
public ICommand UpKeyPressed()
{
var r = new UpKeyPressed();
r.Execute(Paddle1);
return r;
}
}
public class UpKeyPressed : ICommand
{
public void Execute(object parameter)
{
var paddle = parameter as Paddle;
Debug.Assert(paddle != null, "paddle != null");
paddle.IncreaseY();
Debug.WriteLine(paddle.Y);
}
public bool CanExecute(object parameter)
{
return parameter != null;
}
public event EventHandler CanExecuteChanged;
}
}
My XAML page that uses the viewmodel as a dataContext:
<Window x:Class="Pong.Windows.Views.GamePlayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Pong.Core.ViewModels;assembly=Pong.Core"
Title="GamePlayView" Height="350" Width="525">
<Grid>
<Button CommandParameter="{Binding ElementName=Paddle1}"
Command="{StaticResource UpKeyPressed}" >
Click
</Button>
</Grid>
<Window.DataContext>
<local:GamePlayViewModel/>
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{Binding Path=UpKeyPressed}"
Key="O"
Modifiers="Control"/>
</Window.InputBindings>
</Window>
Data structure of my solution
My attempt to fix:
namespace Pong.Core.ViewModels
{
public class GamePlayViewModel
{
private readonly Paddle Paddle1;
private Paddle Paddle2;
private ICommand _doSomething;
public ICommand DoSomethingCommand
{
get
{
if (_doSomething == null)
{
_doSomething = new UpKeyPressed(Paddle1);
}
return _doSomething;
}
}
public GamePlayViewModel()
{
Paddle1 = new Paddle();
Paddle2 = new Paddle();
}
}
public class UpKeyPressed : ICommand
{
private Paddle Paddle1;
public UpKeyPressed(Paddle paddle)
{
Paddle1 = paddle;
}
public void Execute(object parameter)
{
//var paddle = parameter as Paddle;
//Debug.Assert(paddle != null, "paddle != null");
//paddle.IncreaseY();
Paddle1.IncreaseY();
//Debug.WriteLine(paddle.Y);
Debug.WriteLine(Paddle1.Y);
}
public bool CanExecute(object parameter)
{
return Paddle1 != null;
}
public event EventHandler CanExecuteChanged;
}
}
XAML attempt (no errors but not workling upon pressing the 'O' key):
<Window x:Class="Pong.Windows.Views.GamePlayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:Pong.Core.ViewModels;assembly=Pong.Core"
Title="GamePlayView" Height="350" Width="525">
<Grid>
</Grid>
<Window.DataContext>
<viewModels:GamePlayViewModel/>
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{Binding DoSomethingCommand}"
Key="O"
Modifiers="Control"/>
</Window.InputBindings>
Looked at your attempt, there are some things we need to be fix, first your CanExecute should not involve the parameter anymore:
public bool CanExecute(object parameter) {
return Paddle1 != null;
}
Secondly your XAML binding is wrong, you already have DataContext of your view-model flown in your visual tree, you just need a simple Binding with some Path specified like this:
<KeyBinding Command="{Binding DoSomethingCommand}"
Key="O"
Modifiers="Control"/>