MainWindow.xaml
<Grid>
<StackPanel>
<RadioButton Content="A" GroupName="ASD"
Command="{Binding ButtonCommand}"
IsChecked="True"/>
<RadioButton Content="B" GroupName="ASD"
Command="{Binding ButtonCommand}"/>
<RadioButton Content="C" GroupName="ASD"
Command="{Binding ButtonCommand}"/>
</StackPanel>
</Grid>
MainViewModel.cs
public class MainViewModel : BaseViewModel
{
private ICommand _buttonCommand;
public ICommand ButtonCommand { get { return (_buttonCommand ?? new BaseCommand(MyAction)); } }
public void MyAction()
{
Debug.WriteLine("clicked");
}
}
BaseCommand.cs
public class BaseCommand : ICommand
{
private Action _action;
public BaseCommand(Action action)
{
_action = action;
}
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object? parameter)
{
return true;
}
public void Execute(object? parameter)
{
_action();
}
}
When I click the button, call MyAction() and print "clicked", It works right.
When the window is loaded, I want its raise event click, call to MyAction() and print "clicked", so I set the first radiobutton IsChecked property = true, but it do not raise event. Why? And how to solve it, thanks.
Events and commands are not the same. RadioButoon has many events to reflect input events or state changes, but can invoke only a single command.
To know the available events visit the API class reference of the class e.g. by moving the cursor on the class name and then pressing "F1": RadioButton.
In your case you must handle the RadioButton.Checked event. Note that this event will be raised during the initialization routine of the RadioButton in order to fetch the local IsChecked value. If you really need wait until the button is loaded in framework terms (which means layout calculations and rendering have completed), you can defer the Checked event using the Dispatcher with a DispatcherPriority.Loaded or directly handle the RadioButton.Loaded event (instead of the Checked event alongside using the Dispatcher):
<RadioButton Content="A"
GroupName="ASD"
Checked="RadioButton_Checked"
Loaded="RadioButton_Loaded"
IsChecked="True" />
private void RadioButton_Checked(object sender, RoutedEventArgs e)
{
RadioButton radioButton = sender as RadioButton;
// Handle IsChecked changed
radionButton.Command.Execute(default);
// Alternatively, wait until the control has loaded (in case you need to reference layout related properties)
this.Dispatcher.InvokeAsync(() =>
{
radionButton.Command.Execute(default);
}, DispatcherPriority.Loaded);
// If this is a one-time operation, unregister the event handler
radioButton.Checked -= RadioButton_Checked;
}
As mentioned before, if you need the RadioButton to be completely loaded, consider to handle the RadioButton.Loaded event instead of using the Dispatcher.
Related
This is casual and prototype code, hence me trying what I think should work, googling around if it doesn't, then asking here after perusing similar questions.
I have the following markup in my Shell view:
<StatusBarItem Grid.Column="0">
<TextBlock Text="{Binding StatusMessage}" />
</StatusBarItem>
<Separator Grid.Column="1" />
<StatusBarItem Grid.Column="2">
<ProgressBar Value="{Binding StatusProgress}" Minimum="0" Maximum="100" Height="16" Width="198" />
</StatusBarItem>
Then in ShellViewModel I have the following two properties and an event handler:
private string _statusMessage;
public string StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
private double _statusProgress;
public double StatusProgress
{
get => _statusProgress;
set => SetProperty(ref _statusProgress, value);
}
private void OnFileTransferStatusChanged(object sender, FileTransferStatusEventArgs fileTransferStatusEventArgs)
{
StatusMessage = fileTransferStatusEventArgs.RelativePath;
StatusProgress = fileTransferStatusEventArgs.Progress;
}
The event is raised periodically, i.e. every n iterations, from a file download helper class.
Now the strange thing is this, when the event handler updates the vm properties, on the Shell view, the TextBlock bound to StatusMessage updates and displays correctly, but the ProgressBar bound to StatusProgress does not, and remains blank. If I put a break-point in the event handler, I can see the StatusProgress property being properly updated in various values from 0 to 100, yet this does not reflect on the ProgressBar.
The idea of the event handler executing on another thread, which often causes UI update problems, occurred to me, but why is one UI element updating properly and the other not?
NOTE: I have been monumentally stupid and not tested the ProgressBar statically, i.e. just set the viewmodel's StatusProgress to a value and get the shell window to display, without going through the download loop. If I do this, the progress bar displays a length that more or less corresponds to its Value property. None of the layout change suggestions made in comments or answers changes this. Statically it is always visible and always displays a value.
EXAMPLE: I created a small example that believe duplicates the problem. In the example the progress bar doesn't update until the waited on task has completed, and I believe this is the case with my main question, but it was a long download, and I didn't wait for it to complete before noticing the progress bar wasn't updating.
Here is the StatusBar in `MainWindow.xaml:
<StatusBar DockPanel.Dock="Bottom" Height="20">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem Grid.Column="2">
<ProgressBar Value="{Binding StatusProgress}" Maximum="100" Minimum="0" Height="16" Width="198" />
</StatusBarItem>
</StatusBar>
With the code behind in MainWindow.xaml.cs:
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
public MainWindowViewModel ViewModel => (MainWindowViewModel)DataContext;
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
ViewModel.Download();
}
And the code in the MainWindowViewModel:
private string _statusMessage = "Downloading something";
public string StatusMessage
{
get => _statusMessage;
set
{
if (value == _statusMessage) return;
_statusMessage = value;
OnPropertyChanged();
}
}
private int _statusProgress;
public int StatusProgress
{
get => _statusProgress;
set
{
if (value == _statusProgress) return;
_statusProgress = value;
OnPropertyChanged();
}
}
public void Download()
{
var dl = new FileDownloader();
dl.ProgressChanged += (sender, args) =>
{
StatusProgress = args.Progress;
};
dl.Download();
}
And finally the code for FileDownloader:
public class ProgressChangedEventArgs
{
public int Progress { get; set; }
}
public class FileDownloader
{
public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
public void Download()
{
for (var i = 0; i < 100; i++)
{
ProgressChanged?.Invoke(this, new ProgressChangedEventArgs{Progress = i});
Thread.Sleep(200);
}
}
}
In the example, the progress bar remains blank, until FileDownloader finishes its loop, and then suddenly the progress bar shows full progress, i.e. complete.
What's happening
Anything that is not about UI should be done in tasks, because, if not, you're blocking the UI thread and the UI.
In your case, the download was happening on you UI thread, the latter was waiting for the download to finish before updating your UI.
Solution
You need to do two things to solve your problem:
remove the work from the UI thread.
make sure the work can communicate with you UI thread.
So, first, start the download work as a Task like this:
private ICommand _startDownloadCommand;
public ICommand StartDownloadCommand
{
get
{
return _startDownloadCommand ?? (_startDownloadCommand = new DelegateCommand(
s => { Task.Run(() => Download()); },
s => true));
}
}
and connect the button to the command like this:
<Button Command="{Binding StartDownloadCommand}" Content="Start download" Height="20"/>
Then have you download method as such:
public void Download()
{
Application.Current.Dispatcher.Invoke(() => { StatusMessage = "download started"; });
var dl = new FileDownloader();
dl.ProgressChanged += (sender, args) =>
{
Application.Current.Dispatcher.Invoke(() => { StatusProgress = args.Progress; });
};
dl.Download();
Application.Current.Dispatcher.Invoke(() => { StatusMessage = "download DONE"; });
}
The dispatch will have your property (on UI thread) updated from a non UI thread.
And yet, the DelegateCommand helper class:
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute)
: this(execute, null) {}
public DelegateCommand(Action<object> execute,
Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
Remarks
In order to implement the MVVM pattern I had this code behind:
public partial class MainWindow : IView
{
public IViewModel ViewModel
{
get { return (IViewModel)DataContext; }
set { DataContext = value; }
}
public MainWindow()
{
DataContext = new MainWindowViewModel();
}
}
public interface IViewModel {}
public interface IView {}
and this View:
<Window x:Class="WpfApp1.MainWindow"
d:DataContext="{d:DesignInstance local:MainWindowViewModel,
IsDesignTimeCreatable=True}"
xmlns:local="clr-namespace:WpfApp1"
and this ViewModel:
public class MainWindowViewModel: INotifyPropertyChanged, IViewModel
This happens, because StatusBarItem default style sets its HorizontalContentAlignment to Left, which leads ProgressBar to get only a small amount of space horizontally.
You can make the ProgressBar to fill the StatusBarItem completely by setting StatusBarItem's HorizontalContentAlignment to Stretch or you can set the Width of the ProgressBar.
ProgressBar is a DispatcherObject, and DispatcherObject can be only accessed by the Dispatcher it is associated with.
If I understand your question well your OnFileTransferStatusChanged is being triggered on a background thread, so since you're not accessing controls using a Dispatcher (or from the UI thread) you're not guaranteed that the code will work.
The problem is that binding from a non-UI thread usually works until it doesn't - e.g. on a non-dev machine.
like first answer
be sure to be on the main UI thread, because OnFileTransferStatusChanged is on another thread. use this in your event
Application.Current.Dispatcher.Invoke(prio, (ThreadStart)(() =>
{
StatusMessage = fileTransferStatusEventArgs.RelativePath;
StatusProgress = fileTransferStatusEventArgs.Progress;
}));
I made few changes to your sample as your file downloaded is working on UI thread and application just freezes you can see it by changing focus to other application and trying to get back - window will not appear nor update.
changes:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() => ViewModel.Download());
}
forces download to execute in new thread.
public MainWindow()
{
InitializeComponent();
DataContext = ViewModel = new MainWindowViewModel();
}
public MainWindowViewModel ViewModel { get; }
removed cast and access to UI thread only property DataContext.
Now I can see progress bar filling up.
You can't see any changes because your Main thread AKA UI Thread is busy Sleeping
and it does not have time to update your UI
Let Task handle your lengthy job and Main thread for Updating UI
Wrap your code inside Task and you can see your progress bar progressing.
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
_viewModel.Download();
});
//_viewModel.Download(); //this will run on UI Thread
}
In WPF application together with MVVMLight Toolkit, I would like to see your opinion, what is the best way to implement if I need to Cancel the Window Close event.
In Window.Closing event I can set the e.Cancel = true, which prevents closing the form. To identify if the Close is allowed, or should be prevented is in the ViewModel context.
One solution could be if I define an Application variable, and I can query this in the normal event handler in view code behind?
thanks
With MVVM Light you got EventToCommand:
So you could in xaml wire up the closing event to the VM.
<Window ...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding ClosingCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
and in the VM:
public RelayCommand<CancelEventArgs> ClosingCommand { get; private set; }
ctor() {
ClosingCommand = new RelayCommand<CancelEventArgs>(args => args.Cancel = true);
}
If you do not want to pass CancelEventArgs to the VM:
You could always take the similar approach with a Behavior and just use a simple bool from the VM(bind this bool to the Behavior) to indicate the closing event should be cancelled.
Update:
Download Link for following example
To do this with a Behavior you could just have a Behavior such as:
internal class CancelCloseWindowBehavior : Behavior<Window> {
public static readonly DependencyProperty CancelCloseProperty =
DependencyProperty.Register("CancelClose", typeof(bool),
typeof(CancelCloseWindowBehavior), new FrameworkPropertyMetadata(false));
public bool CancelClose {
get { return (bool) GetValue(CancelCloseProperty); }
set { SetValue(CancelCloseProperty, value); }
}
protected override void OnAttached() {
AssociatedObject.Closing += (sender, args) => args.Cancel = CancelClose;
}
}
Now in xaml:
<i:Interaction.Behaviors>
<local:CancelCloseWindowBehavior CancelClose="{Binding CancelClose}" />
</i:Interaction.Behaviors>
Where CancelClose is a bool property from the VM which indicates if the Closing event should be cancelled or not. In the attached example I have a Button to toggle this bool from the VM that should let you test the Behavior
You could to control this using Messages, for instance:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Messenger.Default.Register<CloseApplicationMessage>(this, m => Close());
Loaded += MainWindowLoaded;
Closing += MainWindowClosing;
}
private void MainWindowClosing(object sender, CancelEventArgs e)
{
//Ask for saving
var closingMessage = new ClosingApplicationMessage();
Messenger.Default.Send(closingMessage);
if (closingMessage.Cancel)
e.Cancel = true;
}
...
The mvvm message:
public class ClosingApplicationMessage
{
public bool Cancel { get; set; }
}
In this way, in any place you are listening to the ClosingApplicationMessage, you can control when the application is going to close, and may to cancel it.
Hope this helps...
My Requirement: I want to set custom command for my WPF button, inside the custom command execution I want to know whether the command executed by mouse single click or double click. Also when the customCommand's CanExecute returns false, I want the button to go on disable state. please refer below for more details.
Description:
Hi, In WPF I have set custom command for my button. When I click the button(For both single click and double click) the command gets executed. Inside the custom command I want to handle a separate action for single click and double click. Is it possible to find whetehr button single clicked or double this inside commands?? I use .Net 4.0, c#4.0
Note : I referred this How to bind a command in WPF to a double click event handler of a control? but I faced a limitation here.
Limitation:
When I set the custom command for my button then on CustomCommand CanExcute returns false the button goes to disable state. but As per the above Suggestion, by setting the command to mouse binding and setting the mouse binding to button works but when CanExecute returns false, the button doesnt goes to disable state. How to overcome this
public CustomCommand: ICommandd
{
public bool CanExecute(object parameter)
{
//arbitrary logic
}
public void Execute(object parameter)
{
if(MouseSingleClick)
{
perform ActionA;
}
if(MouseDoubleClick)
{
PerformActionB;
}
}
}
Thanks in Advance.
I was able to use this and tweak it to use it in MVVM friendly way.
I am giving a working example using Cinch framework.
I hoe this helps you give the idea to get going.
MyViewModel
public class MyViewModel : INotifyPropertyChanged
{
private static DispatcherTimer myClickWaitTimer =
new DispatcherTimer (
new TimeSpan (0, 0, 0, 0, 150),
DispatcherPriority.Background,
mouseWaitTimer_Tick,
Dispatcher.CurrentDispatcher);
private static void mouseWaitTimer_Tick (object sender, EventArgs e)
{
myClickWaitTimer.Stop ();
Debug.WriteLine ("Single Click Executed");//PerformActionA
}
public ICommand CinchSingleClickCommand { get; private set; }
public ICommand CinchDoubleClickCommand { get; private set; }
public MyViewModel ()
{
CinchSingleClickCommand = new SimpleCommand<object, EventToCommandArgs> (CanExecuteSingleCinch, ExecuteSingleCinch);
CinchDoubleClickCommand = new SimpleCommand<object, EventToCommandArgs> (CanExecuteDoubleCinch, ExecuteDoubleCinch);
myClickWaitTimer.Stop ();
}
private void ExecuteDoubleCinch (EventToCommandArgs obj)
{
if (obj.EventArgs is MouseEventArgs)
{
myClickWaitTimer.Stop ();
Debug.WriteLine ("Double Click Executed");//PerformActionB
var mouseEvent = obj.EventArgs as MouseEventArgs;
mouseEvent.Handled = true;
}
}
private bool CanExecuteDoubleCinch (object arg)
{
return true;
}
private void ExecuteSingleCinch (EventToCommandArgs obj)
{
if (!(obj.EventArgs is MouseEventArgs))
{
myClickWaitTimer.Start ();
var mouseEvent = obj.EventArgs as RoutedEventArgs;
mouseEvent.Handled = true;
}
}
private bool CanExecuteSingleCinch (object arg)
{
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged (string propertyName)
{
var pc = PropertyChanged;
if (pc != null)
pc (this, new PropertyChangedEventArgs (propertyName));
}
}
You can play with the TimeSpan constructor to set how much delay do you want to keep between the single click and the double click.
The View
<Window x:Class="DataGridTesting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cinch="clr-namespace:Cinch;assembly=Cinch.WPF"
Title="MainWindow"
Height="350"
Width="525">
<DockPanel>
<Button x:Name="button"
Content="Test">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cinch:EventToCommandTrigger Command="{Binding CinchDoubleClickCommand}" />
</i:EventTrigger>
<i:EventTrigger EventName="Click">
<cinch:EventToCommandTrigger Command="{Binding CinchSingleClickCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DockPanel>
</Window>
The Code behind for the view
public partial class MainWindow : Window
{
public MainWindow ()
{
InitializeComponent ();
this.DataContext = new MyViewModel ();
}
}
I used Nuget Package Manager to pull the required dll's for Cinch, System.Windows.Interactivity and Microsoft.Expression.Interactions
The confusion is Button control's default click event shadows over other events like double click, mouse down etc. So May be using a Label is a good idea. You can camouflage it as a button and then use MouseDown and MouseDoubleClick events of the label to do your two different tasks. Use a timer to differentiate single click. Following link shows more detail
double click/ single click
I have a simple-as-can be window with a button tied to a ViewModel with a command.
I expect the button to be disabled if MyCommand.CanExecute() is false. But it seems that WPF will only set the IsEnabled property when the window is first drawn. Any subsequent action does not effect the button's visible state. I am using a DelegateCommand from Prism.
My View:
<Window x:Class="WpfApplication1.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">
<Grid>
<Button Content="Click Here" Command="{Binding MyCommand}" Width="100" Height="50"/>
</Grid>
and my ViewModel:
public class MyVM : NotificationObject
{
public MyVM()
{
_myCommand = new DelegateCommand(DoStuff, CanDoStuff);
}
private void DoStuff()
{
Console.WriteLine("Command Executed");
_myCommand.RaiseCanExecuteChanged();
}
private bool CanDoStuff()
{
var result = DateTime.Now.Second % 2 == 0;
Console.WriteLine("CanExecute is {0}", result);
return result;
}
private DelegateCommand _myCommand;
public ICommand MyCommand
{
get
{
return _myCommand;
}
}
}
50% of the time, when my application loads, the button is properly disabled. However, if it's enabled when the window loads, and I click the button to execute the command, I expect 50% of the time for the button to become disabled, but it never does. The command does not execute, but I can still click the button. How do I get WPF to understand that the button should be disabled when CanExecute() is false?
I see you're using Prism and its NotificationObject and DelegateCommand, so we should expect there not to be a bug in RaiseCanExecuteChanged().
However, the reason for the behaviour is that Prism's RaiseCanExecuteChanged operates synchronously, so CanDoStuff() is called while we're still inside the implementation of ICommand.Execute() and the result then appears to be ignored.
If you create another button with its own command and call _myCommand.RaiseCanExecuteChanged() from that command/button, the first button will be enabled/disabled as you expect.
Or, if you try the same thing with MVVM Light and RelayCommand your code will work because MVVM Light's RaiseCanExecuteChanged calls CommandManager.InvalidateRequerySuggested() which invokes the callback to CanDoStuff asynchronously using Dispatcher.CurrentDispatcher.BeginInvoke, avoiding the behaviour you're seeing with Prism's implementation.
You can try this (Microsoft.Practices.Prism.dll is necessary)
public class ViewModel
{
public DelegateCommand ExportCommand { get; }
public ViewModel()
{
ExportCommand = new DelegateCommand(Export, CanDoExptor);
}
private void Export()
{
//logic
}
private bool _isCanDoExportChecked;
public bool IsCanDoExportChecked
{
get { return _isCanDoExportChecked; }
set
{
if (_isCanDoExportChecked == value) return;
_isCanDoExportChecked = value;
ExportCommand.RaiseCanExecuteChanged();
}
}
private bool CanDoExptor()
{
return IsCanDoExportChecked;
}
}
I want to bind a buttons IsEnabled property to a bool value that is retunred by a WCF web service.
Following is the details...
I have a datagrid in one of my xaml file. In this grid i have a button as part of DataGridTemplateColumn. now i want to data bind the isenable of this button to a bool value returned by the web service.
Can't we done something very simple like..
<Button x:Name="btnUpdRequest" Content="Update" Click="btnUpdRequest_Click" Margin="2" IsEnabled="{Binding isUpdateable}" />
where isUpdateable is one of the value returned by the web service.
Thanks..
What you're describing is very doable, but...first of all, make sure that isUpdatable is a public property that is (a) on the current DataContext, (b) properly raises a PropertyChanged event when it gets updated (or is a DependencyProperty), and (c) is set to the desired initial value. Your web service call will be asynchronous (web service calls in Silverlight are Async) so if you're using anything other than the vanilla Async Model (where you hook an event prior to making your call and set your value within the event handler - and the event handler is guaranteed to be on the UI thread) you may need to marshal your value back to the UI thread to prevent a nasty exception.
Just remember that because of the async nature of the call, the button will update to its desired value only after the call returns, so set the initial value correctly (enabled or disabled, depending on your needs.)
Here's a quick & dirty sample (the service call merely toggles the value it receives as its parameter):
Codebehind:
public partial class MainPage : UserControl, INotifyPropertyChanged
{
public MainPage()
{
InitializeComponent();
}
private bool _isUpdatable;
public Boolean IsUpdatable
{
get { return _isUpdatable; }
set
{
if (_isUpdatable != value)
{
_isUpdatable = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsUpdatable"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void CallTheService_Click(object sender, RoutedEventArgs e)
{
var serviceProxy = new Service1Client();
serviceProxy.ToggleCompleted += new EventHandler<ToggleCompletedEventArgs>(serviceProxy_ToggleCompleted);
serviceProxy.ToggleAsync(IsUpdatable);
}
private void serviceProxy_ToggleCompleted(object sender, ToggleCompletedEventArgs e)
{
if (e.Error == null)
{
IsUpdatable = e.Result;
}
}
}
XAML:
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding ElementName=window1}">
<Button IsEnabled="{Binding IsUpdatable}" Content="Foo" Height="100" VerticalAlignment="Top"/>
<Button Content="Toggle Foo" Height="100" VerticalAlignment="Bottom" Click="CallTheService_Click" />
</Grid>