Hide controls, if their commands shall not be executed - c#

There is an application, which has different permission modes. Depending on the mode, the application has a limited functionality.
Therefore I created my own RestrictedRoutedUICommand class, which inherits from RoutedUICommand and it knows, when it (the command) is allowed to be executed.
This class RestrictedRoutedUICommand has a property Admission, which indicates, if it is allowed to execute the command and an event OnAdmissionChanged which fires, when this property becomes changed.
The Question is: How can I tell those controls, which do have forbidden commands, to hide, if their command is forbidden?
It should be similar to the functionality of controls turning disabled, if their command can not be executed.
Edit: I don't want to use the RoutedUICommand.CanExecute(). Because CanExecute() should only determine, if it is possible to execute the command. So I added another method to the RoutedUICommand which determines if the command is allowed, I want the controls to use this one.

Since Button already becomes disabled you can make Visibility dependant on IsEnabled property
<Button ...>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
or you could make use of BooleanToVisibilityConverter to do the same instead of using Trigger
<Button ... Visibility="{Binding RelativeSource={RelativeSource Self}, Path=IsEnabled, Converter={StaticResource BooleanToVisibilityConverter}}">
EDIT
If these properties are independent then you still can do it via Binding and Converter
<Button ... Visibility="{Binding RelativeSource={RelativeSource Self}, Path=Command.Admission, FallbackValue=Visible, Converter={StaticResource BooleanToVisibilityConverter}}"
this code will bind Visiblity to Command.Admission via BooleanToVisibilityConverter converter and if the value cannot be retrieved, because it's not RestrictedRoutedUICommand or Command is not assigned, then FallbackValue will be use

Related

Validating UserControl's control by Window's ViewModel

Suppose, I have created custom LoginForm as an UserControl, which consists of TextBox and PasswordBox. TextBox has installed custom ErrorTemplate. Naturally, I would like to have this LoginForm as reusable as can, therefore I want to separate validation logic from this LoginForm.
The problem is, that if I bind LoginForm's text property to the "validation-property" of the ViewModel : IDataErrorInfo, that is set as Window's DataContext, the ErrorTemplate is not being applied to LoginForm's TextBox even if I see debug logs from ViewModel's validator.
How can I validate child controls of reusable component via independent ViewModel?
use this error template in application resource:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<Border BorderBrush="Red" BorderThickness="0.8">
<AdornedElementPlaceholder Name="adornerPlaceholder"></AdornedElementPlaceholder>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
After hours of struggling how to solve this, I came up with following solution which satisfies my requirements and so MVVM pattern:
Create in UserControl a DepdendencyProperty of type IDataErrorInfo which will be later implemented by your ViewModel (for my purposes, I used ISignUpValidator:IDataErrorInfo with UsernameValue property).
Lets say this property is registered under the name Validator (as default value I used "do-nothing" implementation of my interface).
Bind UserControl's TextBox.Text property to Validator property:
Lets say TextBox.Text property is exposed to UserControl under the name
Username:
Username="{Binding Path=Validator.UsernameValue, ElementName=UserControlName,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}"
Finally Bind your Viewmodel to your UserControl's Validator property
<Window.Resources>
<local:ViewModel x:Key="ViewModel"/>
<Window.Resources>
<local:LoginForm Validator={StaticResource ViewModel}>
Or alternatively, if your ViewModel is already set as Window's DataContext:
<local:LoginForm Validator="{Binding DataContext, ElementName=WindowName}"}>

Is it possible to use trigger setter to change ViewModel property?

I am opening Popup using IsOpen bound to some hard to reach attached property. All I want is to somehow pass IsOpen value to ViewModel.
Trying to use setter for this:
<Popup StaysOpen="False"
IsOpen="{Binding Path=(local:ListViewBehavior.IsColumnHeaderClicked), RelativeSource={RelativeSource FindAncestor, AncestorType=GridViewColumnHeader}}">
<Popup.Style>
<Style TargetType="Popup">
<Style.Triggers>
<Trigger Property="IsOpen" Value="True">
<!-- this is not possible -->
<Setter Property="{Binding IsPopupOpened ...}" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</Popup.Style>
</Popup>
Gives:
A 'Binding' cannot be set on the 'Property' property of type 'Setter'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Is there a way to pass IsOpen value (which is already bound to something else in the View) to ViewModel?
You could created an attached Property, which is set by eventhandlers to the Opened and Closed Events of the Popup. That can be bind to the IsPopupOpen Property of your ViewModel by OneWayToSource Binding.
I'm also not quite sure if there is an easier Solution, but this is a kind of "Workaround" what I would do, in case that no-one provides a better solution here.

XAML Binding in Style Setter using the Binding Path from the target control

I had an interesting request from a client, there were a number challenges involved, the one that I thought would be the easiest, turned out to be the hardest.
The user needs to know that a value has been changed locally, but not yet persisted to the backend store. (A dirty state) We solved this with a data trigger on a style declared within each control on the page. The control background will be filled with yellow when the value is changed, then reset back to the control default when the save button is pressed.
The ModelView implements a custom interface : ILocalValueCache This has an indexer that should return Boolean to indicate if the current value has changed since the last data refresh.
The ModelView also Implements IDataErrorInfo and uses DataAnnotations Attributes for validation, so I can't simply use validation templates.
What I would like to do is simplify the XAML using a single Style or a control template this is hard because each control now has two bindings, one to Value and another to ValueIsLocal:
To be more specific, I would prefer a solution where the developer doesn't need to know the inner mechanics of how it works (what the field names for the xIsLocal flags are) or if the binding source even supports it.
Because the ViewModel implements this interface, (like IDataErrorInfo) I should be able to globally target control styles bound to the states described by the interface.
Here is a section of the XAML with some of the textboxes:
<TextBox Text="{Binding ScaleName}" Margin="5,2,5,2" Grid.Row="1" Grid.Column="2">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ScaleNameIsLocal}" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="nbScaleCap" Text="{Binding ScaleCap}" Grid.Row="3" Grid.Column="0" Margin="5,2,5,2">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ScaleCapIsLocal}" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="nbTareTol" Text="{Binding TareTol}" Grid.Row="3" Grid.Column="1" Margin="5,2,5,2">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding TareTolIsLocal}" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
As well as the indexer, each property on the View Model has an xxxIsLocal reciprocal property, So the following partial model corresponds to the above example:
string ScaleName { get; set; }
bool ScaleNameIsLocal { get; set; }
string ScaleCap { get; set; }
bool ScaleCapIsLocal { get; set; }
string TareTol { get; set; }
bool TarTolIsLocal { get; set; }
I've played around with using the indexer on the interface to get the IsLocal value but struggled with INotifyPropertyChanged implementation (getting the model to raise the indexer value changed event), that aside the bigger issue was how to make a single style with a binding that is based on the path of the content or text binding on the target control instead of the value of the binding result.
I was inspired by the IDataErrorInfo pattern and using the Validation.ErrorTemplate, it looks simple on the surface and such a simple repetitive pattern like this seems like something that WPF should be able to handle without too many issues.
I am not sure how often I will need this exact template, but it's a pattern that I'm sure I'd like to use again, where there is a potential for each property to have multiple states (not just Error) and to apply a different style using the state as a trigger.
I've edited this post because I haven't quite found what I wanted but thanks to Nikkita I am a step closer :)
By using a custom attached property, we can declare the binding to the flag field directly in the control and can now properly define the style triggers in a global style dictionary.
The ViewModel has not changed, but the XML from above is now simplifed:
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<Trigger Property="att:IsLocal.Value" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</Trigger>
</Style.Triggers>
</Style>
<TextBox Text="{Binding ScaleName}" Margin="5,2,5,2" Grid.Row="1" Grid.Column="2" att:IsLocal.Value="{Binding ScaleNameIsLocal}"></TextBox>
<TextBox Text="{Binding ScaleCap}" Grid.Row="3" Grid.Column="0" Margin="5,2,5,2" att:IsLocal.Value="{Binding ScaleCapIsLocal}"></TextBox>
<TextBox Text="{Binding TareTol}" Grid.Row="3" Grid.Column="1" Margin="5,2,5,2" att:IsLocal.Value="{Binding TareTolIsLocal}"></TextBox>
My biggest issue with the current solution is that I would still need to edit a lot of existing XAML if I wanted to apply this (or another) interface pattern to existing apps. Even in the current form there are over 20 fields, so that's 20 opportunities to get the binding wrong, or to accidentally skip one.
I would suggest you the "validator" pattern (look to spec INotifyDataErrorInfo) combined with custom Behaviour.
Validator crates the collection with results according bound property names in item and Bahaviour change the element. Check the MSDN help.
Xaml Example:
<TextBox
Name="a"
Text="{Binding *Variable*, Mode=OneWay}"
Header="Start"
Style ="{StaticResource MitfahrenDefaultTextEdit}" IsReadOnly="true"
Tapped="StartLocation_OnTapped">
<interactivity:Interaction.Behaviors>
<behaviors:RedFormFieldOnErrors
PropertyErrors="{Binding Path=Record.ValidationCollection[*Variable*]}"/>
</interactivity:Interaction.Behaviors>
</TextBox>

DataTrigger applied to text property of a Textbox causes its binding to stop working

I have a functional binding on my TextBox which acts as a searbar.
However, when I add the following trigger applied on the same binding (The first trigger), it stops the binding.
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Text" Value="{Binding Path=SearchFilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Style.Triggers>
<!-- 1st trigger -->
<DataTrigger Binding="{Binding Path=SearchFilterString}" Value="">
<Setter Property="Text" Value="Type in part name to search."/>
</DataTrigger>
<!-- 2nd trigger -->
<Trigger Property="IsFocused" Value="true">
<Setter Property="Text" Value="{x:Null}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
I also included the second trigger, to make sure after I fix the problem with the first one it won't create a endless loop situation. So please comment on that one as well.
The purpose of these two triggers is to show some guidline inside the TextBox to describe what is this texbox about and the guidline disapears as soon as TextBox gains focus and user attempts to type in the search keyword.Let me know if you believe there is a better approach on doing the same thing.
Your binding won't work, because you are overriding it with your trigger's setter: You are setting the Text property of it to a value, while you are binding the same Text property to the view model. Both things can't work at the same time.
Essentially you're trying to create something that is called "watermark" on a textbox. I like the following the solution in particular, because it doesn't modify the TextBox itself but layers an adorner ontop of it:
Watermark Behavior for TextBox
You are looking for a watermark textbox. The standard WPF TextBox does not support this behavior.
However, there are 3rd party controls available such as the one pointed out by Frank: http://wpfplayground.com/2014/06/30/watermark-behavior-for-textbox/
Ways to do your own can be found at Watermark / hint text / placeholder TextBox in WPF
Finally, you can always just overlay your own TextBox or TextBlock over the real one and hide it on focus, which will do basically the same thing.
This is how I achieved it using Brad's idea.
I simply put a Label with the watermark I wanted and overlapped my TextBox on top of it. All I am doing is setting the TextBox's Background property to Transparent when it's binding content is empty. When this happens you can see the label on the back.
Hence, the moment you start typing watermark disappears.
XAML
<!-- Watermark Label -->
<Label
Foreground="Gray"
Content="Type the part name to start the search."
/>
<!-- Search TextBox -->
<TextBox Grid.Column="0"
HorizontalAlignment="Stretch"
>
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Text" Value="{Binding Path=SearchFilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SearchFilterString}" Value="">
<Setter Property="Background" Value="Transparent"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Outcome

Set focus to edit control within wpf datagrid, depending on visible element

I have a datagrid in which I have defined different edit controls for editing data in one particular column. The visibility of each edit control is bound to the view model, to ensure only one of the edit controls is visible. When the grid switches into edit mode (say, the user selects cell and hits F2), when the edit control becomes displayed, I want it to have focus. However, it only works when I have one edit control. When I have more than 1, it doesn't work. I have determined that this is because the focus code is being executed for the last edit control as defined in the xaml (irrespective of whether it is visible or not).
There are multiple edit controls but for berevity I'll just limit the example here to two.
Within the grid I defined the edit controls and bind the visibility:
<toolkit:DoubleUpDown
Value="{Binding TimeResult, Converter={StaticResource TickToDblSecondsConverter}}"
Visibility="{Binding ResultType, Converter={StaticResource IsTimeValueConverter}}"/>
<toolkit:DoubleUpDown Value="{Binding DistanceResult}"
Visibility="{Binding ResultType, Converter={StaticResource IsDistanceValueConverter}}"/>
This works just fine and the correct control is visible and all the others are hidden. I then wanted to have focus set so I added a call to set the FocusManager.FocusedElement:
<toolkit:DoubleUpDown
Value="{Binding TimeResult, Converter={StaticResource TickToDblSecondsConverter}}"
Visibility="{Binding ResultType, Converter={StaticResource IsTimeValueConverter}}"
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"/>
<toolkit:DoubleUpDown Value="{Binding DistanceResult}"
Visibility="{Binding ResultType, Converter={StaticResource IsDistanceValueConverter}}"
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"/>
However, the focus only gets set correctly if the 2nd control happens to be visible. I need a way to set the FocusedElement to the control that is visible. I thought maybe if it was implemented as a trigger, it would only get fired for the visible control. So I tried giving each control a name and moved the focus code to a DataTemplate.Trigger:
<toolkit:DoubleUpDown Name="timeResultEdit"
Value="{Binding TimeResult, Converter={StaticResource TickToDblSecondsConverter}}"
Visibility="{Binding ResultType, Converter={StaticResource IsTimeValueConverter}}"/>
<toolkit:DoubleUpDown Name="distanceResultEdit" Value="{Binding DistanceResult}"
Visibility="{Binding ResultType, Converter={StaticResource IsDistanceValueConverter}}"/>
<DataTemplate.Triggers>
<Trigger SourceName="timeResultEdit" Property="Visibility" Value="Visible">
<Setter TargetName="timeResultEdit" Property="FocusManager.FocusedElement"
Value="{Binding RelativeSource={RelativeSource Self}}" />
</Trigger>
<Trigger SourceName="distanceResultEdit" Property="Visibility" Value="Visible">
<Setter TargetName="distanceResultEdit" Property="FocusManager.FocusedElement"
Value="{Binding RelativeSource={RelativeSource Self}}" />
</Trigger>
</DataTemplate.Triggers>
But this makes not the slightest bit of difference.
So, how do I change this so that I can have x number of edit controls, and focus will be set to that visible control when edit mode is invoked in the grid?
Using FocusManager.FocusedElement in this way does nothing. That property tells an element which of its descendant controls has logical focus, i.e., which control should receive keyboard focus when keyboard focus enters the target element. You are setting this property on the elements you wish to receive keyboard focus, and since the problem was that they don't have keyboard focus to begin with, nothing is accomplished.
Set FocusManager.FocusedElement on the root element within your cell template instead.
Another solution would be to add a PreparingCellForEdit handler on your DataGrid, then schedule a focus traversal request to the first visible and focusable element within the cell editor:
private void OnDataGridPreparingCellForEdit(
object sender,
DataGridPreparingCellForEditEventArgs e)
{
if (e.Column == YourColumn) // replace with an appropriate comparison
{
var element = e.EditingElement;
element.Dispatcher.BeginInvoke(
DispatcherPriority.Input,
new Action(() => element.MoveFocus(
new TraversalRequest(FocusNavigationDirection.First))));
}
}

Categories

Resources