WPF MVVM Custom TItle - c#

I decided to make a custom title for my program in WPF and encountered difficulties.
I started to study MVVM pattern and its essence to get rid of using standard events in View.
I wanted to make buttons to close, minimize and maximize window, but ran into difficulties. I can't understand where the logic of these buttons should be.
If you don't use standard events, but use commands, it won't work, because ViewModel doesn't know anything about the window. And I don't want to use events.
I found this solution for window close button
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:CallMethodAction MethodName="Close"
TargetObject="{Binding RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType=Window}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
But I don't know how to do the other two buttons the same way. I tried to find other MethodNames that can be used here, but I found only method Hide, but it does not suit me, because it hides the window completely, it is neither on the taskbar nor in the tray, but it is still running and visible in the task manager.
Can you tell me how I can do the same window minimizing and resizing through XAML code?
UPD:
I found a way to minimize the window, but I still do not know how to make a button that will change the WindowsState to Normal if the WindowState Maximized and vice versa.
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:ChangePropertyAction PropertyName="WindowState"
TargetObject="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Value="{Binding Source={x:Static sys:WindowState.Minimized}}"
/>
</i:EventTrigger>
</i:Interaction.Triggers>

In MVVM any UI related logic must be in the View. This should be clear.
Commands are not View Model only. Your View can also define commands. Special ICommand implementation is the RoutedCommand. Don't think that because it is a command, then it must be handled in the View Model.
You should not use the Interaction.Triggers, especially not in your scenario. Additionally, if you are not firm with MVVM, Interaction.Triggers will very likely introduce code smells.
Simply create an event handler for the Button.Click event in your Window class' code-behind e.g., MainWindow.xaml.cs file.
Also, there is no reason to bind to a static variable or a constant or an enum. Just reference it directly:
<i:ChangePropertyAction Value="{x:Static sys:WindowState.Minimized}" ... />
However, to solve your problem simply add an event handler in your code-behind:
MainWindow.xaml
<Button Click="OnMaximizeButtonClicked"
Content="Toggle Maximize" />
MainWindow.xaml.cs
// Toggle the WindowState between Maximized and Normal
private void OnMaximizeButtonClicked(object sender, RoutedEventArgs e)
=> this.WindowState = this.WindowState == WindowState.Normal
? WindowState.Maximized
: WindowState.Normal;
Alternatively, use routed commands (How to: Create a RoutedCommand):
MainWindow.xaml
<Button Command="{x:Static local:MainWindow.ToggleMaximizeStateCommand}"
Content="Toggle Maximize" />
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static RoutedCommand ToggleMaximizeStateCommand { get; } = new RoutedCommand("ToggleMaximizeStateCommand", typeof(MainWindow));
public MainWindow()
{
InitializeComponent();
// Register the command handler
var toggleMaximizeStateCommandBinding = new CommandBinding(
ToggleMaximizeCommand,
ExecuteToggleMaximizeStateCommand,
CanExecuteToggleMaximizeStateCommand);
this.CommandBindings.Add(toggleMaximizeCommandBinding);
}
// Toggle the WindowState between Maximized and Normal
private void ExecuteToggleMaximizeStateCommand(object sender, ExecutedRoutedEventArgs e)
=> this.WindowState = this.WindowState == WindowState.Normal
? WindowState.Maximized
: WindowState.Normal;
private void CanExecuteToggleMaximizeStateCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = true;
}

Related

MVVM Light EventToCommand not always fired

I'm using MVVM Light in WPF application and trying to bind Window's Closing event with a command implemented in ViewModel.
But when I do it in XAML the event handler is called randomly, right after starting the app it is usually firing, but after a 20 or 30 minutes of app running, the app closes immediately without firing "Closing" handler.
<Window
[...]
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight"
[...]
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding Source={StaticResource Locator},
Path=Main.ExitAppCmd,
Mode=OneWay}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Window>
The command is declared in MainViewModel as:
public RelayCommand<CancelEventArgs> ExitAppCmd { get; private set; }
and is set to event handler by:
ExitAppCmd = new RelayCommand<CancelEventArgs>((args) => ExitAppHandler(args));
When the app is closed there are no exceptions.
I was trying to set breakpoint on the event handler, but in cases when the app closes immediately, the breakpoint is not executed.
When I bind the same handler in code behind, the handler is always fired.
private void Window_Closing(object sender, CancelEventArgs e)
{
var model = DataContext as MainViewModel;
model.ExitAppHandler(e);
}
How can I debug what's wrong with XAML binding ?

Unable to focus ListView

Situation: In MVVM pattern, I have some inputbindings on a listview which work only when the listview is focused. However, whenever user clicks, the listview goes out of focus and user is unable to execute the inputbindings.
Problem: I want to bring the focus on the listview (on button click) in a way that the inputbindings work.
What I tried:
I tried using attached property IsFocused (where I focus using UIElement.Focus() and/or Keyboard.Focus()) and binding it to a bool variable in the ViewModel which I would set using an ICommand.
I also tried a separate example where I can use the System.Windows.Input.Keyboard.Focus(item) method in the code behind (I mean the .xaml.cs file with the same name) to focus the listview and it works! But, I don't know how to implement the similar thing in a ViewModel which is connected using a d:DesignInstance attribute.
I believe that the mouseclick event is bubbled up and handled somewhere else which causes the list to unfocus as soon as I click it. Like, if I find a way to set the event as handled that will help, but again I don't know how to do that in a viewmodel. Here is my attached property :
FocusExtension.cs
public static class FocusExtension {
public static bool GetIsFocused(DependencyObject obj) {
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value) {
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached(
"IsFocused", typeof(bool), typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
private static void OnIsFocusedPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e) {
var uie = (UIElement)d;
if ((bool)e.NewValue) {
uie.Focus();
}
}
}
XAML File:
<ListView
x:Name="lv"
Grid.Column="2" Margin="2" MinWidth="250" Height="400" ToolTip="the List"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding ListBindingInVM}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="False"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding }"
behaviour:ListViewAutoScroll.AutoScrollToEnd="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
>
<ListView.Style>
<Style TargetType="ListView" >
<Setter Property="ViewModels:FocusExtension.IsFocused" Value="{Binding ListFocused, Mode=TwoWay}"></Setter>
<!--The one below is not clean, but worked. However, list goes out of focus on click. -->
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="ViewModels:FocusExtension.IsFocused" Value="True"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListView.Style>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<!--This command sets the ListFocused to true-->
<i:InvokeCommandAction Command="{Binding BringListToFocus }"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.InputBindings>
<!-- Bindings that don't work when list is not focused-->
<KeyBinding Modifiers="Control" Key="C" Command="{Binding CopyCommand}"/>
<KeyBinding Modifiers="Control" Key="V" Command="{Binding PasteCommand}"/>
</ListView.InputBindings>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy" Command= "{Binding CopyCommand}"></MenuItem>
<MenuItem Header="Paste" Command= "{Binding PasteCommand}"></MenuItem>
</ContextMenu>
</ListView.ContextMenu>
The focus behavior that you describe is easily implemented from the codebehind, and doing so does not violate the MVVM pattern. Consider Josh Smith's post, below:
https://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090097
The use of a ViewModel here makes it much easier to create a view that
can display a Customer object and allow for things like an
"unselected" state of a Boolean property. It also provides the ability
to easily tell the customer to save its state. If the view were bound
directly to a Customer object, the view would require a lot of code to
make this work properly. In a well-designed MVVM architecture, the
codebehind for most Views should be empty, or, at most, only contain
code that manipulates the controls and resources contained within that
view. Sometimes it is also necessary to write code in a View's
codebehind that interacts with a ViewModel object, such as hooking an
event or calling a method that would otherwise be very difficult to
invoke from the ViewModel itself.

Showing a progress dialog when loading a View/UserControl using MVVM: Which events should I use?

I want to show a loading dialog while opening a View/UserControl that takes a while to open. I know I can use Loaded event to close the dialog once the UI is laid out and shown. I am doing this like in the sample code that is shown below.
My question is: Is there any event that I can use in a similar manner to open the dialog when the layout/loading process starts?
I could open the loading dialog from the ViewModel that opens this View/UserControl, but this would spread out the logic. Furthermore, since I am using messaging (from MvvmLight) to signal the View/UserControl to load it this would result in an unclean solution IMO. So any ideas how to achieve this?
View:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding ViewLoadedEventHandlerCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
ViewModel:
private ICommand viewLoadedEventHandlerCommand;
public ICommand ViewLoadedEventHandlerCommand
{
get
{
if (viewLoadedEventHandlerCommand == null)
viewLoadedEventHandlerCommand = new RelayCommand(() => Debug.WriteLine("MainView was loaded."));
return viewLoadedEventHandlerCommand;
}
}

WPF: Bind to datacontext in modal parent window

I am opening a modal window using:
public void PropertiesTablesButtonClicked(object sender, RoutedEventArgs e)
{
Window _childWindow = new PropertiesTablesWindow();
// Assign MainWindow as the owner of this window, this will cause the MainWindow
// to become inactive and make the child window flash if the main window is clicked
_childWindow.Owner = App.Current.MainWindow;
_childWindow.ShowDialog();
}
Is there a way from within PropertiesTablesWindow.xaml to bind to the DataContext of the main window? The main window DataContext has a property EditMode which lets me know if the program is in edit mode which in turn would be used to make a DataGrid on the child window read-only or editable like so:
<DataGrid Name="PropertiesDataGrid"
ItemsSource="{Binding PropertiesDataView, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="False"
CanUserAddRows="False"
MaxHeight="200"
IsReadOnly="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Application}}, Path=DataContext.EditMode,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource NegatedStringComparisonToBooleanConverter}, ConverterParameter=Admin}">
I have tried AncestorType of Window and Application but obviously these do not work.
Different windows have different visual trees. That's why you failed with the bindings. But why not to set the modal window's datacontext on the datacontext of the owner window? It'd do the trick. Of course you could also build a mediator to store the context but the first solution is so tempting simple.

How to Bind to window's close button the X-button

How I can bind one of my buttons on control to X Button that closes the window ?
I just want to create cancel button that just closes the window.
I am using MVVM in my code.
If possible to do it only in xaml, I just dont have any special code with the button click.
You can just call the Close() method, which will close the window.
private void MyButton_Click(object s, RoutedEventArgs e)
{
Close();
}
If it's WPF (and provided I remember right) you can just use CallMethodAction from the parent as a behavior and utilize Close() method via just XAML. Something like;
Parent Window x:Name="window"
namespaces;
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
-
<Button Content="Cancel">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction
TargetObject="{Binding ElementName=window}"
MethodName="Close"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Hope this helps.
MVVM solution without code-behind could also look like this:
View:
<Button Content="Cancel" Command="{Binding CloseWindowCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
ViewModel:
public ICommand CloseWindowCommand
{
get
{
return new RelayCommand<Window>(SystemCommands.CloseWindow);
}
}
But SystemCommands is from .net-4.5 so if you rock in some older version of .net you can also use following.
public ICommand CloseWindowCommand
{
get
{
return new RelayCommand<Window>((window) => window.Close());
}
}

Categories