MahApp Flyout CloseCommand and IsOpen are Not Binding Properly - c#

I'm following a strict MVVM pattern.
In my FlyoutControl, I've bound the following:
<controls:FlyoutsControl>
<controls:Flyout IsOpen="{Binding FlyoutIsOpen, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
CloseCommand="{Binding CloseFlyoutCommand}">
...
</controls:Flyout>
</controls:FlyoutsControl>
I have two scenarios, both which do not work:
Scenario 1:
I set FlyoutIsOpen to true in my ViewModel constructor, and I bind by CloseFlyoutCommand to a DelegateCommand which accepts a method that sets FlyoutIsOpen to false.
In this scenario, my view loads with the Flyout already open (as expected). However, when I click the Flyout close button, nothing happens unless I click it again. If I print the output of my method, I can confirm that the command sets FlyoutIsOpen to false, but for some reason, I require a second click (after FlyoutIsOpen is set to false) to actually close the Flyout.
Scenario 2:
I set FlyoutIsOpen to false (or uninitialized) in my constructor. I bind another button to a DelegateCommand which accepts a method that sets FlyoutIsOpen to true.
The view loads with the Flyout closed (as expected). However, when I click a button that I've bound to a method that sets FlyoutIsOpen to true, nothing happens and the Flyout does not appear.
Has anyone experienced similarly non-responsive issues with the FlyoutsControl? If so, how did you resolve it?

I also had problems binding the isOpen property, but I managed by styling the ItemContainer and binding IsOpen there instead of inside my Flyout. I don'T even need the CloseCommand anymore, setting Visible to false in my viewmodel closes the flyout.
The MainWindow.xaml where I define the ItemContainer Bindings:
<controls:MetroWindow.Flyouts>
<controls:FlyoutsControl ItemsSource="{Binding Flyouts}">
<controls:FlyoutsControl.Resources>
<view:FlyoutPositionConverter x:Key="FlyoutPositionConverter"/>
</controls:FlyoutsControl.Resources>
<controls:FlyoutsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:SettingsViewModel}">
<view:SettingsFlyout/>
</DataTemplate>
</controls:FlyoutsControl.ItemTemplate>
<controls:FlyoutsControl.ItemContainerStyle>
<Style BasedOn="{StaticResource {x:Type controls:Flyout}}"
TargetType="{x:Type controls:Flyout}">
<Setter Property="Header"
Value="{Binding Header}" />
<Setter Property="IsOpen"
Value="{Binding Visible}" />
<Setter Property="Position"
Value="{Binding Position, Converter={StaticResource FlyoutPositionConverter}}" />
<Setter Property="IsModal"
Value="{Binding IsModal}" />
<Setter Property="Theme" Value="Accent" />
</Style>
</controls:FlyoutsControl.ItemContainerStyle>
</controls:FlyoutsControl>
</controls:MetroWindow.Flyouts>
The mainviewmodel has a ObservableCollection<IFlyoutViewModel> named Flyouts and contains all my flyout viewmodels. They of course have to implement IFlyoutViewModel:
using System.ComponentModel;
namespace MyApplication.ViewModel
{
internal interface IFlyoutViewModel : INotifyPropertyChanged
{
string Header { get; }
bool Visible { get; set; }
Position Position { get; set; }
bool IsModal { get; set; }
}
public enum Position
{
Top,
Left,
Right,
Bottom
}
}
The FlyoutPositionConverter is just a mapper between my position enum and the MahApps.Metro.Controls.Position because I didn't want to use the real positon in my viewmodel interface.

Related

How can i use one Viewmodel, with multiple views/pages which can bind to variables in Viewmodel

Confused embedded programmer here
I am making a WPF application that will have multiple views/pages. I want the views to be full screen and be able to click through via buttons.
View 1:
Landing Welcome page, with a next button on
View 2:
Select Comport Page, User selects Comport then click through
View 3:
Main Comport Application Page
I don't want to make 3 separate view models as view 3 will require the comport selection from View 2. The only way I have found to switch the views requires you to switch the data context. However, then I cannot access data in the other view models.
If anyone knows how this could be achieved please let me know!
If you don't want three different view models, you could add a property to the main view model that decides which view to display:
public class MainViewModel : INotifyPropertyChanged
{
public string SharedSampleProperty { get; set; } = "Some value that is shared across all views...";
private View _currentView = View.First;
public View CurrentView
{
get => _currentView;
set { _currentView = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public enum View
{
First,
Second,
Third
}
In the view, you could then use a ContentControl and a Style that sets the ContentTemplate based on the value of this property:
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate x:Key="{x:Static local:View.First}">
<TextBlock>1...</TextBlock>
</DataTemplate>
<DataTemplate x:Key="{x:Static local:View.Second}">
<TextBlock>2...</TextBlock>
</DataTemplate>
<DataTemplate x:Key="{x:Static local:View.Third}">
<TextBlock>3...</TextBlock>
</DataTemplate>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentView}" Value="First">
<Setter Property="ContentTemplate" Value="{StaticResource {x:Static local:View.First}}" />
</DataTrigger>
<DataTrigger Binding="{Binding CurrentView}" Value="Second">
<Setter Property="ContentTemplate" Value="{StaticResource {x:Static local:View.Second}}" />
</DataTrigger>
<DataTrigger Binding="{Binding CurrentView}" Value="Third">
<Setter Property="ContentTemplate" Value="{StaticResource {x:Static local:View.Third}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
You may obviously replace the TextBlock elements with UserControls in the DataTemplates.
I don't want to make 3 separate view models as view 3 will require the comport selection from View
One way to use a single VM is to create a page where the navigation, such as button clicks actions, is accomplished by just hiding/showing specific controls based on a bound state variable. Meaning the the Visiblity of all controls on the screen is tied to that bound state value; some are visible and some are hidden based on the value found.
If you have an application which is not too large, hiding and showing what looks like to the user a new page view by turning off the old items and turning on the new can be done.
Avoid this design if the app will have more than roughly 25 controls which need such orchestration.

Notify user that command could not be executed

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.

Accessing Validation Errors in a UserControl

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);
}

How can I get the "Selected MenuItem" in WPF

I was wondering how can I get the "Selected" MenuItem from a Menu.
Basically, I want to get the "Selected" MenuItem so I can sort my ListBox.
Here is my XAML for the Menu.
<Menu>
<MenuItem Header="Sort by" ItemsSource="{Binding SortByOptions}"
*SelectedItem="{Binding GroupBy}"*/>
</Menu>
I Switched my ComboBox with a Menu, but in Menu, "SelectedItem" does not exist like in ComboBox. I was wondering how could I get what Item from menu was chosen.
C#
The ItemsSource Binding "SortByOptions" is an ObservableCollection of string who contains the options to sort.
The binding "GroupBy" is a String that is set each time the user chose another MenuItem.
I am searching to set the variable "GroupBy" every time the user chose another MenuItem.
Before, my ComboBox worked well.
SOLUTION
I needed to specify the style of the property "Command" and "CommandParameter" like this:
<Menu Layout="Text" Margin="10,0,0,0">
<MenuItem Header="Group by" ItemsSource="{Binding GroupByOptions}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command"
Value="{Binding ViewModel.GroupCommand, RelativeSource={RelativeSource AncestorType={x:Type Views:MyView}}}" />
<Setter Property="CommandParameter" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
Note that the CommandParameter is the actual "Header" chosen by the user. (This is what I was searching for) I did not know, but when you do {Binding} it takes the actual string.
And in my ViewModel, here is what it looks like:
private ICommand mSortCommand;
//Implement get and set with NotifyPropertyChanged for mSortableList
private ICollectionView mSortableList;
public ICommand SortCommand
{
get { return mSortCommand ?? (mSortCommand = new RelayCommand(SortMyList)); }
}
public void SortMyList(object sortChosen)
{
string chosenSort = sortChosen as string;
CampaignSortableList.SortDescriptions.Clear();
Switch(chosenSort){
"Sort my List"
}
CampaignSortableList.Refresh();
}
It works all fine now.

Binding a MenuItem's Command via MVVM

I have a system set up where a ContextMenu hierarchy is populated dynamically using a MVVM architecture. All of my bindings function properly except for Command. My view is a ContextMenu that specifies an ItemContainerStyle.
The ContextMenu's ItemContainerStyle is set to this:
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Command, Mode=OneWay}"/>
<Setter Property="IsCheckable" Value="{Binding IsCheckable}"/>
<Setter Property="IsChecked" Value="{Binding IsChecked, Mode=TwoWay}"/>
<Setter Property="Header" Value="{Binding Label}"/>
<Setter Property="ItemsSource" Value="{Binding Children}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContextMenu.ItemContainerStyle>
So far, there is no ItemTemplate, as seemingly I have been able to accomplish all the desired functionality in the style.
The ViewModel must be constructed with an instance of the model it wraps, so it seems the DataContext of the ContextMenu cannot be explicitly set to the ViewModel (the compiler complains that it does not have a parameterless constructor. The complaint mentions that a type converter can also be used, though I am unsure as to what that actually means (could solve the issue).
The relevant pieces of my ViewModel are as follows, starting with the two following read-only public facing members that are available to be bound to:
public CommandBinding CommandBinding { get; private set; }
public RoutedCommand Command { get { return CommandBinding.Command as RoutedCommand; } }
CommandBinding and its command are instantiated in the constructor:
CommandBinding = new CommandBinding(new RoutedCommand(), CommandBinding_Executed, CommandBinding_CanExecute);
The methods referred to in that construction simply operate on members of the model, and are implemented as follows:
void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
if (ContextItem.Command != null) ContextItem.Command(ContextItem);
}
void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = ContextItem.IsEnabled;
if (ContextItem.ExecuteConditions != null) e.CanExecute = ContextItem.ExecuteConditions.GetInvocationList().Cast<ExecuteCondition>().All(s => s() == true);
}
It seems that when the binding to Command actually works, all of the items appear dimmed, as if CanExecute were returning false. However, when I set a breakpoint in CanExecute, execution never breaks at that point (though perhaps this is due to layout threading?). Even if I explicitly set e.CanExecute to true and comment out the other lines in CommandBinding_CanExecute, the items still appear dimmed. In XAML, I have tried binding to both the Command and CommandBinding members with and without Path=, all to the same effect. When I set the binding mode to OneWayToSource, the debugger appropriately complains that the property is read-only and cannot be operated on (I want the ViewModel to provide the command, so this is intended).
I have read the other examples and solutions to related issues. For those that follow the MVVM pattern, I cannot determine how my implementation differs.
For any solution, I must insist that I can still require the ViewModel to be constructed with the model as a parameter, and I would prefer the view to remain all XAML, with no C# code behind.
Seems the CommandBinding was the issue. I ended up creating my own implementation of ICommand that allows me to specify Execute and CanExecute delegates on construction... which worked perfectly.
This fixed the problem and the implementation was simple, but it is still unclear to me as to why my use of CommandBindings was not working.

Categories

Resources