WPF: DataTemplate-Style gets only applied if the Command can execute - c#

Another strange behavior in my MVVM-Adventures I can't explain and didn't find a reasoning:
I have a Command in my VM, which can be executed, when a Color is selected:
public ICommand SetSingleColor
{
get
{
return new RelayCommand(f =>
{
var visualLed = f as VisualLed;
visualLed.Color = SelectedColor.Value;
}, () => SelectedColor.HasValue);
}
}
My DataTemplate is looking like this:
<DataTemplate DataType="{x:Type ml:VisualLed}" x:Key="DtVisualLed">
<Button
Background="DarkCyan"
Command="{Binding Path=DataContext.SetSingleColor, RelativeSource={RelativeSource AncestorType=v:LedDesignView}}"
CommandParameter="{Binding}"
Style="{StaticResource StyleBtnLed}">
</Button>
</DataTemplate>
As u can see, I pass the VisualLed itself back to the VM, so I can set the Color. I just added the Background-Property for testing purposes.
What I really don't get: The Style and also the Background are only applied, if the Command can be executed! So if I load the View, the Button-Background is the Default-Gray, and as soon as I select a Color, it becomes DarkCyan.
Unfortunately, I can't give more Informations, but I didn't find anything on the Topic, how ButtonBase.Command influences other Properties. It also seems like a expected behavior, since I don't get any Binding-Errors or such.

There is relation between Button.Command and Button.IsEnabled property. If Command.CanExecute returns false, button becomes disabled. Now, you often cannot control everything with your own style, if control developer did not plan for this. In this case, disabled button background is fixed by developers of the Button and you cannot override it with your own style (unless you change Template of the button).

Related

C# - RaisePropertyChanged called multiple times and throwing stackoverflow exception

I'm fairly new to WPF. I have the following radio button in my application
<Viewbox Height="30">
<RadioButton Content="B1" GroupName="InputBelt" IsChecked="{Binding RBChecked, Mode=TwoWay, FallbackValue=True}" VerticalAlignment="Center"/>
</Viewbox>
<Viewbox Height="30">
<RadioButton Content="B2" GroupName="InputBelt" IsChecked="{Binding RBChecked, Converter={StaticResource boolconverter}, Mode=TwoWay}" VerticalAlignment="Center"/>
</Viewbox>
I have defined datacontext in xaml file
<Window.DataContext>
<vm:TestViewModel />
</Window.DataContext>
The issue is when the page is loaded for the 1st time, everything is fine. But when I go to some other page in the application and comes back to this page, the application crashes due to stackoverflow exception.
I even tried adding datacontext locally in radiobutton tag but it isn't working either.
Property structure given below.
private bool _bRBChecked;
public bool RBChecked
{
get { return _bRBChecked; }
set
{
_bRBChecked = value;
RaisePropertyChanged("RBChecked");
}
}
Upon investigating further, I found out that the RaisePropertyChanged of the binded property is being called too many times. This issue occurs only with the property binded to radio button. I have other controls which has two-way bind with other properties in the same page and it seems to work fine.
Now I have tried the below fix suggested in another stackoverflow question and it seems to be working.
set
{
if (_bRBChecked != value)
{
_bRBChecked = value;
RaisePropertyChanged("RBChecked");
}
}
But I would like to know the root cause of this issue and why the property is being set so many times and find any alternate fix if possible. Please let me know if I am missing anything.
Any insight is highly appreciable.
Your change notification is not protected from recursion. Property A changing Property B, whose change changes Property A...
A simple solution is this:
set
{
if(value != _bRBChecked){
_bRBChecked = value;
RaisePropertyChanged("RBChecked");
}
}
Simply check if the value is actually a change, before you go tell everyone about it. This pattern is explicitly used in the Examples. I am 90% sure the Depdency Properties have a similar recursion protection, but it would not be the first time I was wrong.
I think it is fairly easy to figure this out, based on the fix you shared.
What happens in steps:
You set the new value in one of the radio buttons
The event is raised
Since it's two way binding, the binding of the second radio button sets the value again to the other radio button
The event is raised again due to 3
Go back to 1 as now the value is set again for the first radio button.
With your fix the value is not set (the setter it's not called) so the event is not triggered again.

wpf xaml button with AccessText seems not to query CanExecute

Explanation
Aim: In my wpf desktop application I want to add AccessText to a button that is bound to a command that has a predicate which evaluates if the window / control is busy and thus enables/disables the button.
Status: The button command works fine and is properly enabled prior to adding AccessText. (I'm using MvvmLight for my ICommand support.)
Problem: After adding AccessText the command still binds but the CanExecute seems to no longer get queried and the button is always enabled. (My ideal solution would be no code-behind just XAML.)
Example
XAML:
Working:
<Button Command="{Binding NavToStoresSearchCmd}" Content="Stores" Height="30"/>
Not Working:
<Button Command="{Binding NavToStoresSearchCmd}" Height="30">
<AccessText>S_tores</AccessText>
</Button>
C# (ViewModel):
public ICommand NavToStoresSearchCmd { get => new RelayCommand(OnNavToStoresSearch, () => IsNotBusy); }
(The code for IsNotBusy and the OnNavToStoresSearch command work fine, as does CanExecute UNTIL I add the AccessText.)
Having just tested this, it appears to be working as expected.
My code:
XAML:
<Button Command="{Binding BrowseCommand}">
<AccessText>_Browse</AccessText>
</Button>
C# (ViewModel):
In constructor:
BrowseCommand = new RelayCommand( BrowseCommandHandler, () => CanBrowse );
'CanBrowse' property:
private bool _canBrowse;
public bool CanBrowse
{
get { return _canBrowse; }
set { _canBrowse = value; BrowseCommand.RaiseCanExecuteChanged(); }
}
My guess is you're not calling 'RaiseCanExecuteChanged()' on your RelayCommand?
(Edit: Just seen the comments come in before I hit submit. Sorry folks!)
As said in the comments, RaiseCanExecuteChanged is needed here. WPF's CommandManager calls CanExecute when it detects UI changes, such binding updates, state changes, etc. It is unreliable. You may have been lucky in your earlier testing in how the CommandManager was reacting for you, but (in my opinion) it's better to explicitly call RaiseCanExecuteChanged when you know it's changed.
(Further edit)
I did a quick bit of searching around and it appears you can get around needing to call 'RaiseCanExecuteChanged' by changing a namespace entry. Change...
using GalaSoft.MvvmLight.Command;
for
using GalaSoft.MvvmLight.CommandWpf;
I quickly ripped the 'RaiseCanExecuteChanged()' code out from my test app and shockingly it appears to work.
Maybe I've been doing it wrong all these years...

Context-sensitive command with MVVM

I have a custom component that is basically a text box with an attached button. The button is supposed to perform an action on the text box; for example clicking the button could fill the text box with some random string.
The text fields are bound to properties in the ViewModel. It basically looks like this:
What would be the best way to set up a commanding that is general to the component?
What I did so far is that I have a single general RelayCommand in my ViewModel that expects a parameter. Each button has its command set to that single command and I use the CommandParameter property to add some information about which text field component I am actually talking about. The ViewModel then uses that information to find out the correct property and change its value (updating the text boxes via binding).
While this works fine, I dislike that I have to manually insert the information about the related text box or context. Ideally, I would like to have the command executed within a context-scope that already knows which text box or bound property it is talking about. Is there some way to do this?
Another problem I have run into is that I want to bind the button action to a key command. So when I’m focussing a text box and press a key shortcut, I want it to behave as if I have clicked the correct button, i.e. execute the command and pass the correct context information. My alternative would be to put this into the code-behind and basically extract the command parameter from the current focus, but I’d prefer a cleaner solution.
Is there any good way to make this work with MVVM?
How about something along these lines:
public class TextBoxAndCommandPresenter : INotifyPropertyChanged
{
private readonly Action<TextBoxAndCommandPresenter> _action;
public TextBoxAndCommandPresenter(string description,
Action<TextBoxAndCommandPresenter> action)
{
Description = description;
_action = action;
}
public string Description { get; private set; }
public string Value { get; set; }
public ICommand Command
{
get { return new DelegateCommand(() => _action(this)); }
}
}
Used like this:
var presenter = new TextBoxAndCommandPresenter("Field 1",
p => p.Value = "hello world");
With XAML:
<UserControl ...>
<UserControl.Resources>
<DataTemplate DataType="{x:Type TextBoxAndCommandPresenter}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Description}"/>
<TextBox Text="{Binding Value}"/>
<Button Command="{Binding Command}">Click</Button>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<ContentPresenter Content="{Binding}"/>
</UserControl>
As I already had a custom control for the text box and the button combination, creating a UserControl wasn’t really a necessary option for me. My control exposes bindable properties for the button’s command and command parameter, and for now, I’m sticking with what I have explained in the question; using the command parameter to update the corresponding property in the view model that is then updated via data binding.
Depending on how repetitive it will become later, I might encapsulate that in either multiple custom controls or build a similar helper as Scroog1 showed.
As for the key command, which was actually my primary concern, I realized that this is ultimately something the view alone should handle. So my view model is completely oblivious of the key command.
I know have a standard command binding to the window’s code-behind that looks up the currently focused element, checks if it is of the type of my custom control and then simply executes the underlying command. So the code-behind is essentially just delegating the command execution to the focused control.
While this is not a perfect solution, as I’d rather have some actual “context sensitivity” for commands, this is working fine for now and still separates the view from the logic correctly.

How to handle WPF event in MVVM for nested controls in a Window

FINAL NOTE
Final solution found in another post
Although I appreciated the clarification that was provided, the ultimate solution was in-fact provided by another solution as linked above. No matter WHAT I tried, the binding via the "Element Name" component was not working. I had to go based on the "Relative" hierarchy up the datagrid...
<Button Name="btnPrintReport"
Command="{Binding DataContext.MyPrintCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}"
Height="16" Width="16" HorizontalAlignment="Center" >
<Image Source="MyButtonImage.png" IsHitTestVisible="True"/>
</Button>
Hope something not too complicated in WPF / MVVM environment. Here's the scenario.
I have a Window (.xaml) and a corresponding View Model (.cs). The form displays fine with all the data bindings no problem. (note: this is NOT done via any commercial "framework" )
One of the controls that is in the view window is a custom user control of a datagrid with all pre-defined columns, headings and content to be displayed when the view is shown. This works all no problem even though the control is not directly "defined" in the main window .xaml file, but just dropped on the form as the user control itself (which has its own obvious .cs code-behind).
With the main window's "DataContext" pointing to the View Model, and the user control that has a datagrid
<DataGrid AutoGenerateColumns="False"
Name="dataMyStuff"
ItemsSource="{Binding Path=MyTablePropertyOnViewModel,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}" ... />
Now, what I'm looking for. On this data grid, I have a column that has an image in the first column. When I click on this image, I want to print a report specific to the record as represented by this row (it has a PK value I use). So, how do I tell the image "KeyUp" event to go to the View Model event handler since that is where the data is, and some other methods I'll need for preparing the call to the report. The view portion of the grid is for cosmetic display to the user, and thus no "functionality" directly in this control.
-- EDIT -- per progress from answers
I've adjusted my datagrid per comments from Josh and Rachel, however, something still does not appear to be quite right... Seeing the button was using a "Command" instance, I interpreted this as it needed to attach to an instance of an "ICommand" interface object on my view model. So, I created an instance. I know the command handler works as it is also used for common things like Add, Edit, Save, Cancel, Exit, etc... So I have a new one for this printing purpose. For simplicity, I have it created as an Always Execute, so there is no method to handle the "CanExecute" portion of the control. I've set the button's "Command" to almost all iterations I could think of an still nothing, but here's an update of what I'm seeing going on.
<UserControl>
<Data grid columns / template, etc to the button>
<DataTemplate>
<Button Name="btnPrintReport"
Command="{Binding DataContext.MyPrintCommand}" >
<Image Source="myPrintImage.png"/>
</Button>
</DataTemplate>
</Data grid columns, etc>
</UserControl>
In my ViewModel class (myICommandButtonHandler inherits from ICommand)
private myICommandButtonHandler myPrintCommand;
public myICommandButtonHandler MyPrintCommand
{
get { if (myPrintCommand == null)
myPrintCommand = new myICommandButtonHandler(myPrint);
return myPrintCommand;
}
}
private void myPrint()
{
MessageBox.Show( "Doing the print job..." );
}
Now, what I'm seeing. During step through initialization of all the controls and such. I click menu item to call my Window to be displayed. FIRST, it creates an instance of the View Model controller. THEN, it calls the Window and passes in the View Model controller as parameter so it is immediately set at the Window level as the "DataContext" of the window. The main window then goes into it's "InitializeComponents" call and starts to build out all the other controls embedded, including this separate class that contains the data grid in question. At the constructor of this usercontrol (that has the datagrid), there is no "data context" set yet as the rest of the controls have not been initialized yet, and I don't know why / when the "bindings" apparently get "hooked" in. So, it appears that trying to do the binding to the data grid's command button are failing. HOWEVER, at run-time, the actual data IS updated in the grid, so I know that much is working.
So, the data grid has its "ItemsSource" set to a property of a "DataView" property on the view model, but the binding of the "button" doesn't appear to see the "MyPrintCommand" handler that I thought would get the hit.. and its action is to just display a message box (for now).
Usually I use an AttachedCommand Behavior which allows me to bind Events to ViewModel Commands. For example, you could use
<Image ...
local:CommandBehavior.Event="KeyUp"
local:CommandBehavior.Command="{Binding DataContext.PrintCommand, ElementName=dataMyStuff}"
local:CommandBehavior.CommandParameter="{Binding }"/>
I'd would recommend using a different event than KeyUp, since I don't think Images can have Keyboard focus so the KeyUp event will never get fired.
A better alternative is to use a Button and overwrite it's Template to be your Image. This will maintain the Click functionality, and give you access to Command and CommandParameter properties
<Button Command="{Binding DataContext.PrintCommand, ElementName=dataMyStuff}"
CommandParameter="{Binding }">
<Button.Template>
<Image ... />
</Button.Template>
</Button>
Also, the CommandParameter="{Binding }" will simply pass the current DataRow's DataContext (your data object) to the command
Change the data template to be a button that has a image as its content. Use the command and commandparameter properties on the button to call your printing method. You can declare your print command in your viewmodel, and bind to it. Your parameter could be the selected row in your datagrid.

RelayCommand CanExecute behavior not working

I'm having trouble getting the RelayCommand to enable/disable the attached control properly.
I've got an EventToCommand element attached to a button. The command is databound to the ViewModel. Initially, the button is disabled (expected behavior), but I cannot seem to get the CanExecute logic to check it's value. When CurrentConfigFile is set and exists, the button should be enabled. I've executed code and checked the file's value in debug to make sure it's set, but the control is still disabled. I've tried CommandManager.InvalidateRequerySuggested() and command.RaiseCanExecuteChanged(), but it will not enable.
I've wondered if lambdas don't work correctly for the CanExecute behavior (even though the examples use them) or that the CanExecute behavior needs to be databound to another element.
Here's my code:
// The FileInfo being checked for existence before the button should be enabled
public const string CurrentConfigFilePN = "CurrentConfigFile";
public FileInfo CurrentConfigFile
{
get
{
return _currentConfigFile;
}
set
{
if (_currentConfigFile == value)
{
return;
}
var oldValue = _currentConfigFile;
_currentConfigFile = value;
// Update bindings, no broadcast
RaisePropertyChanged(CurrentConfigFilePN);
}
}
public MainViewModel()
{
// snip //
SaveCommand = new RelayCommand(SaveConfiguration,
() => CurrentConfigFile != null && CurrentConfigFile.Exists);
}
private void SaveConfiguration()
{
// export model information to xml document
ExportXMLConfiguration(CurrentConfigFile);
}
and markup
<Button x:Name="SaveButton" Content="Save" Width="75" Margin="20,5">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft:EventToCommand x:Name="SaveETC"
Command="{Binding SaveCommand}"
MustToggleIsEnabledValue="true" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Update:
As per Isak Savo's suggestion, I bound the RelayCommand directly to the button with
<Button x:Name="SaveButton" Content="Save" Width="75" Margin="20,5"
Command="{Binding SaveCommand}"/>
and it started disabled and correctly enabled when the FileInfo was set. Guess I should remember not to fix what isn't broken!
Why don't you just bind to the Command directly from the Button?
<Button Command="{Binding SaveCommand}" Content="Save" />
Maybe the EventToCommand thing you are using is messing things up with the command's CanExecute notification.
And regarding the CanExecute problem - are you sure that your CanExecute handler is called after the CurrentConfigFile property is set? I've found that even though WPF mostly does a good job of requerying CanExecute, I still sometimes need to force a requery through the CommandManager.
EDIT: As pointed out in the comments, the OP has already tried the command manager approach.
In msdn is written:
When first called, FileInfo calls Refresh and caches information about the file. On subsequent calls, you must call Refresh to get the latest copy of the information.
However, I would not do such a check in the CanExecute-handler. This may slow down your UI because CanExecute is called a lot of times and I could imagine that such IO-checks can become slow, for example if the file lies on a network share.

Categories

Resources