I have a control defined in Silverlight as follows:
<HyperlinkButton x:Name="testHyperlink" Content="Test" FontWeight="Bold" Click="testHyperlink_Click">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction PropertyName="Visibility" TargetName="panel1"
Value="Collapsed" />
<ei:ChangePropertyAction PropertyName="Visibility" TargetName="panel2"
Value="Visible" />
</i:EventTrigger>
<i:EventTrigger>
<ei:ChangePropertyAction PropertyName="Visibility" TargetName="panel1"
Value="Visible" />
<ei:ChangePropertyAction PropertyName="Visibility" TargetName="panel2"
Value="Collapsed" />
</i:EventTrigger>
</i:Interaction.Triggers>
</HyperlinkButton>
This hyperlink is part of a DataTemplate. That is the reason I'm using the triggers. When someone clicks the HyperlinkButton, an asynchronous process is fired. When the process has completed, I want to execute the second trigger. Essentially, I'm flipping the visibility of some content.
My question is, when my event is finished, how do I fire the second EventTrigger associated with the HyperlinkButton?
It's incorrect using of Interactivity EventTriggers. Answering directly your question, you can do next (I'm writting that only because I coudn't write that it's impossible, but I'm ashamed for this solution):
create own action with public Invoke
public class MyChangePropertyAction: ChangePropertyAction
{
public new void Invoke(object parameter)
{
base.Invoke(parameter);
}
}
Use it instead Interactivity ChangePropertyAction. Now you can get invoke action directly from code behind:
((MyChangePropertyAction)Interaction.GetTriggers(testHyperlink)[1]).Invoke(parameter);
But, I believe that you can simply use MVVM approach and do next:
create bool property IsBusy with property changed notification in view model;
bind it to your "panel1" Visibility property via BooleanToVisibility converter;
bind command DoServiceCall from view model to "testHyperlink" Command property;
and in view model make service calls and change IsBusy property to true or false depending on should you display panel or not.
Good luck
Related
I have a XAML file in my Xamarin project that displays different views depending on the state of 2 picker views. The Picker View is a custom view that lets you display an Enumerator as a picker. The important part is, that the SelectedItem does fire the PropertyChanged notification.
So in my Xaml I define my style like this:
<Style x:Key="SinglePressureRelativeHumidity" TargetType="ContentView">
<Style.Triggers>
<MultiTrigger TargetType="ContentView">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference Mode}, Path=SelectedItem}"
Value="{x:Static enums:HumidityCalculatorMode.SinglePressure}" />
<BindingCondition Binding="{Binding Source={x:Reference KnownValue}, Path=SelectedItem}"
Value="{x:Static enums:HumidityCalculatorKnownValue.RelativeHumidity}" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="IsVisible" Value="true" />
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
</Style>
This is placed inside the local resource dictionary. The References Mode and KnownValue refer to the custom picker views, which are correctly defined in the same xaml file.
I later have a StackLayout with multiple ContentPages each looking similar to the following:
<ContentView Style="{StaticResource SinglePressureRelativeHumidity}"
IsVisible="False">
<StackLayout>
<controls:TemperatureEntry Title="Temperature"
Temperature="{Binding HumidityCalculator.InputTemperature, Mode=TwoWay}"/>
<controls:PressureEntry Title="Test Pressure"
Pressure="{Binding HumidityCalculator.InputPressure, Mode=TwoWay}" />
</StackLayout>
</ContentView>
Where each ContentPage has its own Style with different Conditions.
Now to the problem, when I change the value of any of the picker the ContentPages get enabled or disabled as you would expect, the one where the style's MultiTrigger's Conditions are met gets set to visible all other are set to invisible.
However, the problem is that when loading the view all are set to invisible. So it is as if the trigger only checks when there are changes made by the user. I have tested various things.
First I tried setting the value of both pickers to the wanted default value after the InitializeComponent method without success. I made sure that the Property SelectedItem does fire the PropertyChanged notification with the correct name.
Second I tried inverting the isVisible property of the ContentViews to true but then all were visible which also wasn't what i wanted.
So how can I trigger the MultiTrigger with my default values?
I was able to fix this by binding the BindingCondition directly to the Model which the Pickers set their values to.
I am not sure why this fixed the issue.
I have a TextBox that is tied to a command like this:
<TextBox Text="{Binding Path=TextContent, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Command="{Binding Path=MyCommand}" Key="Enter" />
</TextBox.InputBindings>
</TextBox>
The property TextContent is a string defined in the ViewModel. The command MyCommand is also defined in the ViewModel. The ViewModel does not know the View.
The command will be called whenever the TextBox has focus and the enter key is hit. Unfortunately, if CanExecute returns false, the user cannot see (visually) that the command was not executed, because there is no visual change in the TextBox.
I am looking for advice on how to show the user that the command could not be executed after he had pressed enter.
My ideas (and my doubts about them):
Disabling the TextBox when CanExecute returns false: This is no option because the return value of CanExecute can change everytime a letter is typed/changed (the text in the TextBox influences the outcome of CanExecute). When it is disabled for the first time, the user cannot type into it any more, so it will stay disabled forever.
Show a message box saying that the command was not executed: Remember, the ViewModel does not know the View. Is it even possible to open a message box from the ViewModel? Furthermore, where should I put the call to opening a message box? Not inside CanExecute because I only want to get the message box after hitting enter, not everytime CanExecute returns false. Maybe make CanExecute always return true and do the checks inside Execute: If checks are okay, do the command stuff, if not, show some message to the user. But then, the point of having CanExecute is missed entirely...
I want to keep MVVM, but some codebehind for redirecting stuff to the ViewModel seems okay for me.
I suggest the following solution.
Here's an example on how to notify the user which I'm working on at the moment.
I want the user to type in a data limit which is of type int, double or string.
It want to check so the user type in the correct type.
I use a property ValidateLimits which checks the string MyLimits which in your case is TextContent.
Everytime the user type in anything in the TextBox, ValidateLimits will check the string. If it is not a valid string in the textbox then return false otherwise return true.
If false then highlight it with the DataTrigger by setting some Properties on the TextBox which in my case is some Border and Foreground colors, also a ToolTip.
Also in your case you want to call your Validate method in your CanExecute method.
If you already have a function for checking that the command is OK then just add it to the DataTrigger binding.
<TextBox Text="{Binding MyLimit1, UpdateSourceTrigger=PropertyChanged}" Margin="-6,0,-6,0">
<TextBox.Style>
<Style TargetType="TextBox">
<!-- Properties that needs to be changed with the data trigger cannot be set outside the style. Default values needs to be set inside the style -->
<Setter Property="ToolTip" Value="{Binding FriendlyCompareRule}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ValidateLimits}" Value="false">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="ToolTip" Value="Cannot parse value to correct data type"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
public bool ValidateLimits
{
get
{
// Check if MyLimit1 is correct data type
return true/false;
}
}
Use a Property bool IsCommandExecuted in your Commandclass. Set this property accordingly.
Use a ToolTip and bind its IsOpen property to IsCommandExecuted property like this :
<TextBox ...>
<TextBox.ToolTip>
<ToolTip IsOpen="{Binding MyCommand.IsCommandExecuted}">...</ToolTip>
</TextBox.ToolTip>
</TextBox>
This explains the concept, modify it accordingly.
I created a WPF (.Net 4) UserControl containing some ComboBoxes and a TextBox. In XAML, some ValidationRules are bound to the TextBox. If the TextBox contains invalid data, a red frame is shown, and the tooltip is set to the error description. Works well.
Next, I placed two instances of that UserControl on a form, and added a button. In XAML, the button is connected to a RelayCommand of the ViewModel. Now I want the button to be enabled only when both of the UserControls contain valid data only.
Not a problem, I thought, let me use a strategy which works elsewhere. I added a trigger:
<Button Content="_OK" ... Command="{Binding Path=OKCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="false" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=cascadingComboFrom, Path=(Validation.HasError)}" Value="false" />
<Condition Binding="{Binding ElementName=cascadingComboTo, Path=(Validation.HasError)}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
But there is a catch: Validation.HasError is always false for the UserControls - the Validation failed for an element inside the UserControl, not for the UserControl proper.
I know that I can register the Click event of the button, check the validity there using the method shown in Detecting WPF Validation Errors, and prevent the execution of the RelayCommand by setting the Handled property of the RoutedEventArgs to true. But that looks more like WTF than WPF.
What do you suggest? How can I retrieve the Validation Errors of the UserControl's children? Or how can I get them in the RelayCommand's CanExecute method? Or some other tricks?
You can set a property on the command binding called ValidatesOnDataErrors.
Implementation would look something like this:
<Button Content="_OK" Command="{Binding, Path=OKCommand, ValidatesOnDataErrors=True}"/>
You can read more about it here.
The Button.IsEnabled property is already hard wired to the CanExecute method of your RelayCommand, so all you need to do is to set that return value to false when the form fields are invalid:
private bool CanExecute(object commandParameter)
{
return areFormFieldsValid;
}
Now, how you set the bool areFormFieldsValid variable to true or false is up to you... there are several ways of doing that. Personally, I prefer to use the IDataErrorInfo interface, which has a handy Error property that you can check. There are many online tutorial on how to implement this, so I won't repeat that here... however, the end result is something like this:
private bool CanExecute(object commandParameter)
{
return string.IsNullOrEmpty(yourDataObject.Error);
}
This feels like a terribly basic question but I am sure there is a better way to do this. I have a Button in my UI which selects a specific tab and fire a Command from the ViewModel
Here is the current code (which works fine):
XAML:
<Button Content="Button!" Click="OnButtonClick" Command="{Binding WhateverCommand}" />
Code behind:
private void OnButtonClick(object sender, RoutedEventArgs e)
{
theTab.IsSelected = true;
}
Isn't there any cleaner, XAML-only way to do that UI operation? I was thinking about something like:
<Button Content="Button!" Click="OnButtonClick" Command="{Binding WhateverCommand}">
<Button.Triggers>
<EventTrigger RoutedEvent="Click">
<Setter TargetName="theTab" Property="IsSelected" Value="True" />
</EventTrigger>
</Button.Trigger>
</Button>
But unfortunately it seems like the EventTrigger won't support a Click event. Why so? I am still sometimes confused with triggers after a few years working in WPF, and this pretty much sums it up. When trying to build that I have an error on the Setter line:
A value of type 'Setter' cannot be added to a collection or dictionary of type 'TriggerActionCollection'.
Thank you!
EDIT since I was ask the XAML structure of my Window, it looks like this:
<DockPanel LastChildFill="True">
<Ribbon DockPanel.Dock="Top">
<Button Content="Button!" Click="OnButtonClick" Command="{Binding WhateverCommand}" />
</Ribbon>
<TabControl>
<TabItem x:Name="theTab" />
</TabControl>
</DockPanel>
Error sums it up. You cannot use Setter in EventTrigger. If you want to do it in XAML you can use Storyboard that will select given tab when button is pressed. Something like this:
<Button Content="Click">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="theTab" Storyboard.TargetProperty="IsSelected">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
There definitely is a better way. With the help of the Windows.Interactivity assembly you are able to bind the event source to a singe class, containing only the associated action. With this you can almost do everything you ned.
The action class has to derive from TriggerAction. By overriding the Invoke-method you can specify the action.
Despite this scenario it also possible to bind the EventTrigger to a command (e.g. relay command), allowing a clean MMVM implementation.
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<Button x:Name="button">
<i:Interaction.Triggers>
<i:EventTrigger SourceName="button" EventName="Click">
<app:MyAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Public Class MyAction
Inherits Interactivity.TriggerAction(Of UIElement)
Protected Overrides Sub Invoke(parameter As Object)
MsgBox("Clicked")
End Sub
End Class
I updated the code to meet your specific requirements. The TriggerAction class now also contains a dependency property, which can be cound to your tab control:
<TabControl x:Name="tab"/>
<Button x:Name="button">
<i:Interaction.Triggers>
<i:EventTrigger SourceName="button" EventName="Click">
<app:MyAction Target="{Binding ElementName=tab}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Public Class MyAction
Inherits Interactivity.TriggerAction(Of UIElement)
Protected Overrides Sub Invoke(parameter As Object)
DirectCast(Target, TabControl).SelectedIndex = 0
End Sub
Shared Sub New()
_targetProperty = DependencyProperty.Register(
"Target",
GetType(UIElement),
GetType(MyAction),
New UIPropertyMetadata(Nothing))
End Sub
Private Shared _targetProperty As DependencyProperty
Public Shared ReadOnly Property TargetProperty As DependencyProperty
Get
Return _targetProperty
End Get
End Property
Property Target As UIElement
Get
Return DirectCast(GetValue(TargetProperty), UIElement)
End Get
Set(value As UIElement)
SetValue(TargetProperty, value)
End Set
End Property
End Class
You can introduce a bool property IsMyTabSelected to your VM, bind it to TabItem.IsSelected:
<TabItem x:Name="theTab" IsSelected="{Binding IsMyTabSelected}" >
Then you just set this flag in the WhateverCommand handler for the Button.
Note: The IsMyTabSelected property must implement System.ComponentModel.INotifyPropertyChanged.
I am trying to bind a RelayCommand's CanExecute in my main window to a child window that possibly does not exist. How should I do it?
Currently I have:
<MenuItem Header="_Compact"
Command="{Binding Path=CurrentChildViewModel.CompactCommand}"
IsEnabled="{Binding CurrentChildViewModel.CanExecuteCompactCommand,
Converter={StaticResource NullToBooleanConverter}}"/>
However this does not seem to work because the converter should work on CurrentChildViewModel (and not the CanExecuteCompactCommand, but I also should include that CanExecuteCompactCommand somehow.
I want the menu item to be enabled only if CurrentChildViewModel != null and CurrentChildViewModel.CanExecuteCompactCommand() returns true.
(reason: the CurrentChildViewModel is a window's ViewModel that can be opened or not, if it is not opened, I want the menu item to be disabled. And if it is opened, I want the Compact command's CanExecute method to check if the compact command can be executed, which is something like at least two items in the listview in the ChildView (Model) are selected.)
Can anybody help please?
if your converter need the instance of CurrentChildViewModel then bind to that and not the command (remove .CanExecuteCompactCommand)
That said why on earth are you using one command to determine if another command should be able to execute? You should utilize the CanExecute of your command (CompactCommand).
Ok I think I understand your actual problem now.
If I'm correct then your xaml/bindings work as expected unless either CurrentChildViewModel or CanExecuteCompactCommand is null. (assuming you remove your converter.)
To solve this you can add FallbackBalue=false to your binding, this tells the binding to use false when it cannot find the source. And also add TargetNullValue=false this tells the binding to use false when the source is null (CompactCommand in this case)
So it would look like:
IsEnabled="{Binding CurrentChildViewModel.CanExecuteCompactCommand,
FallbackValue=false,
TargetNullValue=false}"
That said I still would discourage the usage of a command to determine if another command can execute. I would do would do something like this:
e.g.
<Style TargetType="{x:Type MenuItem}" x:Key="menuItemWithCommand">
<Style.Triggers>
<Trigger Property="Command" value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
...
<MenuItem Header="_Compact"
Style="{StaticResource menuItemWithCommand}"
Command="{Binding Path=CurrentChildViewModel.CompactCommand}" />
...
CompactCommand= new RelayCommand(CompactCommandExecuted, CompactCommandCanExecute);
private void CompactCommandExecuted(obejct obj)
{ // Do your business
}
private bool CompactCommandCanExecute(object obj)
{
// return true if the command is allowed to be executed; otherwise, false.
}