Window fade out in MVVM - c#

I have a ViewModel, from which I show a Window that should fade out. It fades only first time, and then It stops.
public class MessageBoxViewModel
{
private MessageBoxView _message;
private MessageBoxResult _result = MessageBoxResult.No;
public MessageBoxViewModel()
{
//...creating commands...
}
private void Window_Closing(object sender, CancelEventArgs e)
{
//Close window with fade out animation
_message.Closing -= Window_Closing;
e.Cancel = true;
var animation = new DoubleAnimation
{
To=0,
Duration=TimeSpan.FromSeconds(1),
FillBehavior = FillBehavior.Stop
};
animation.Completed += (s, a) => { _message.Close(); };
_message.BeginAnimation(UIElement.OpacityProperty, animation);
}
public MessageBoxResult Show(string Text, string Title)
{
//...setting properties which View is bound to
_message = new MessageBoxView
{
DataContext = this
};
_message.Closing += Window_Closing;
_message.ShowDialog();
return _result;
}
}
And this is how I call messagebox in different ViewModel:
class SomeViewMNodel : INotifyPropertyChanged
{
private MessageBoxViewModel _message = new MessageBoxViewModel();
public SomeViewModel()
{
//....
}
private void ShowMessages(object parameter) //Command on click of some button
{
_message.Show("Hey I'm fading.", "Fade out"); //Fading is succesfully done
_message.Show("Hey I'm fading second time.", "Fade out"); //Fading doesn't work anymore
}
}
I have tried to stop an animation as suggested here, but that doesn't seem to work. Neither does Opacity property actually change - a simple check with var j = _message.GetAnimationBaseValue(UIElement.OpacityProperty) ==> allways shows value of 1, in animation.Completed or after inicializing new Window.
I've figured that animation works If I don't use _message variable, but instead declare a new instance of ViewModel, e.g. var win = new MessageBoxViewModel(). But I'm using this custom MessageBox for all errors & notifications in many ViewModels, so I would like to use only _message variable If possible (I would make it global).
Without MVVM and re-initiliazing instance of Window I can make animation working everytime, but how can I animate Window in MVVM properly?

I don't see the point in making the Window instance global and reuse the Window instance. You should generally avoid creating global (static) instances.
Creating a small Window instance from time to time is quite affordable. Instantiation costs are unnoticeable in this case.
Anyway, if you want to reuse a Window instance, you are not allowed to close it. Closing a Window disposes its unmanaged resources.
If you want to use Window.Close(), you have to override the virtual Window.OnClosing method (or listen to the Window.Closing event) and cancel closing and instead set the window's visibility to Visibilty.Collapsed:
private void Window_Closing(object sender, CancelEventArgs e)
{
//Close window with fade out animation
_message.Closing -= Window_Closing;
e.Cancel = true;
var animation = new DoubleAnimation
{
To=0,
Duration=TimeSpan.FromSeconds(1),
FillBehavior = FillBehavior.Stop
};
animation.Completed += (s, a) => _message.Visibility = Visibility.Collapsed; ;
_message.BeginAnimation(UIElement.OpacityProperty, animation);
}
But as some have noticed already, this implementation is violating the MVVM pattern. You are not allowed to introduce a coupling between the view and the view model. The goal of MVVM is to remove this exact coupling.
The view model is not allowed to handle any UI components.
The following example shows how implement a dialog infrastructure that complies with the MVVM pattern.
The example consists of four simple steps
Create a DialogViewModel
Create the Dialog, which will extend Window. It will use a DataTemplate to show its content based on the DialogViewModel (or subtypes) in the Dialog.DataContext
Create a public property e.g., DialogViewModel in your view model that needs to show the dialog.
Let the parent control e.g. MainWindow actually show the Dialog.
Example implementation
Implement a view model that serves as the data source for the actual dialog window:
DialogViewModel.cs
This view model defines an AcceptCommand and a CancelCommand which can be bound to corresponding dialog buttons.
When one of the commands is executed a CloseRequested event is raised.
The constructor takes a delegate which serves as a callback that is invoked when the dialog was closed.
public class DialogViewModel : INotifyPropertyChanged
{
public DialogViewModel(string dialogCaption, string message, Action<DialogViewModel> dialogClosedHandler)
{
this.DialogCaption = dialogCaption;
this.Message = message;
this.DialogClosedHandler = dialogClosedHandler;
}
public void HandleResult() => this.DialogClosedHandler.Invoke(this);
private string dialogCaption;
public string DialogCaption
{
get => this.dialogCaption;
set
{
this.dialogCaption = value;
OnPropertyChanged();
}
}
private string message;
public string Message
{
get => this.message;
set
{
this.message = value;
OnPropertyChanged();
}
}
public ICommand AcceptCommand => new RelayCommand(param => this.IsAccepted = true);
public ICommand CancelCommand => new RelayCommand(param => this.IsAccepted = false);
private bool isAccepted;
public bool IsAccepted
{
get => this.isAccepted;
set
{
this.isAccepted = value;
OnPropertyChanged();
OnCloseRequested();
}
}
public event EventHandler<DialogEventArgs> CloseRequested;
public event PropertyChangedEventHandler PropertyChanged;
private Action<DialogViewModel> DialogClosedHandler { get; }
protected virtual void OnCloseRequested()
{
this.CloseRequested?.Invoke(this, new DialogEventArgs(this));
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Implement the dialog window, which will show its content based on the DialogViewModel using a DataTemplate. To create different types of specialized dialogs, simply ceate a specialized dialog view model and a corresponding DataTemplate.
Every eventual dialog animation is also implemented in this class using XAML and EventTrigger, which triggers on a DialogClosed routed event.
The Dialog will listen to the DialogViewModel.CloseRequested event to close itself. Since you wished to reuse the Dialog instance, the Dialog intercepts the invocation of Close() to collapse itself. This behavior can be enabled using the constructor.
After closing itself, the Dialog sets the DialogEventArgs.Handled property to true, which will trigger the invocation of the dialog closed callback (which was registered with the DialogViewModel), so that the calling view model, that showed the dialog, can continue to execute:
Dialog.xaml.cs
public partial class Dialog : Window
{
#region DialogClosedRoutedEvent
public static readonly RoutedEvent DialogClosedRoutedEvent = EventManager.RegisterRoutedEvent(
"DialogClosed",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(Dialog));
public event RoutedEventHandler DialogClosed
{
add => AddHandler(Dialog.DialogClosedRoutedEvent, value);
remove => RemoveHandler(Dialog.DialogClosedRoutedEvent, value);
}
#endregion
private bool IsReuseEnabled { get; }
public Dialog(bool isReuseEnabled = false)
{
InitializeComponent();
this.IsReuseEnabled = isReuseEnabled;
this.DataContextChanged += OnDialogViewModelChanged;
}
public Dialog(DialogViewModel dialogViewModel) : this()
{
this.DataContext = dialogViewModel;
}
private void OnDialogViewModelChanged(object sender, DependencyPropertyChangedEventArgs e)
{
HandleDialogNewViewModel(e.OldValue as DialogViewModel, e.NewValue as DialogViewModel);
}
private void HandleDialogNewViewModel(DialogViewModel oldDialogViewModel, DialogViewModel newDialogViewModel)
{
if (oldDialogViewModel != null)
{
oldDialogViewModel.CloseRequested -= CloseDialog;
}
if (newDialogViewModel != null)
{
newDialogViewModel.CloseRequested += CloseDialog;
}
}
private void CloseDialog(object sender, DialogEventArgs e)
{
Close();
e.Handled = true;
}
#region Overrides of Window
protected override void OnClosing(CancelEventArgs e)
{
if (!this.IsReuseEnabled)
{
return;
}
e.Cancel = true;
Dispatcher.InvokeAsync(
() => RaiseEvent(new RoutedEventArgs(Dialog.DialogClosedRoutedEvent, this)),
DispatcherPriority.Background);
base.OnClosing(e);
}
#endregion
private void DialogClosedAnimation_OnCompleted(object? sender, EventArgs e)
{
this.Visibility = Visibility.Collapsed;
}
}
Dialog.xaml
To customize the layout, edit the DataTemplate:
<Window x:Class="Dialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Height="450"
Width="800"
Title="{Binding DialogCaption}">
<Window.Resources>
<!--
To create more specialized dialogs,
create a dedicated DataTemplate for each dialog view model type.
-->
<DataTemplate DataType="{x:Type local:DialogViewModel}">
<StackPanel>
<TextBlock Text="{Binding Message}"/>
<StackPanel Orientation="Horizontal">
<Button Content="Ok" Command="{Binding AcceptCommand}" />
<Button Content="Cancel" IsDefault="True" IsCancel="True" Command="{Binding CancelCommand}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</Window.Resources>
<!-- Animations triggered by the DialogClosed event -->
<Window.Triggers>
<EventTrigger RoutedEvent="local:Dialog.DialogClosed">
<BeginStoryboard>
<Storyboard Completed="DialogClosedAnimation_OnCompleted">
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:1" FillBehavior="Stop"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" FillBehavior="Stop">
<DiscreteObjectKeyFrame KeyTime="0:0:1" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
<Grid>
<ContentPresenter Content="{Binding}"/>
</Grid>
</Window>
To trigger the displaying of the Dialog let the view model create the DialogViewModel and assign it to a public property.
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public void SaveToFile(object data, string filePath)
{
// Check if file exists (pseudo)
if (string.IsNullOrWhiteSpace(filePath))
{
// Show the dialog
this.DialogViewModel = new DialogViewModel("File Exists Dialog", "File exists. Replace file?", OnDialogResultAvailable);
}
else
{
Save(data, filePath);
}
}
public void Save(object data, string filePath)
{
// Write data to file
}
private void OnDialogResultAvailable(DialogViewModel dialogViewModel)
{
if (dialogViewModel.IsAccepted)
{
// User has accepted to overwrite file
Save(data, filePath);
}
}
private DialogViewModel dialogViewModel;
public DialogViewModel DialogViewModel
{
get => this.dialogViewModel;
set
{
this.dialogViewModel = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
To actually show the dialog, let the parent Window (e.g., MainWindow) listen to the property changes of ViewModel.DialogViewModel e.g., by setting up a Binding:
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public static readonly DependencyProperty CurrentDialogViewModelProperty = DependencyProperty.Register(
"CurrentDialogViewModel",
typeof(DialogViewModel),
typeof(MainWindow),
new PropertyMetadata(default(DialogViewModel), MainWindow.OnDialogViewModelChanged));
public DialogViewModel CurrentDialogViewModel
{
get => (DialogViewModel) GetValue(MainWindow.CurrentDialogViewModelProperty);
set => SetValue(MainWindow.CurrentDialogViewModelProperty, value);
}
private static void OnDialogViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
{
return;
}
(d as MainWindow).ShowDialog(e.NewValue as DialogViewModel);
}
private void ShowDialog(DialogViewModel dialogViewModel)
{
this.Dialog.DataContext = dialogViewModel;
this.Dialog.ShowDialog();
// Alternative recommended approach:
// var dialog = new Dialog(dialogViewModel);
// dialog.ShowDialog();
}
private Dialog Dialog { get; set; }
public MainWindow()
{
InitializeComponent();
// Create a reusable dial instance (not recommended)
this.Dialog = new Dialog(true);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel).SaveToFile(null, string.Empty);
}
}
MainWindow.xaml
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<Style TargetType="local:MainWindow">
<-- Observe view model DialogViewModel property using data binding -->
<Setter Property="CurrentDialogViewModel" Value="{Binding DialogViewModel}" />
</Style>
</Window.Resources>
<Button Content="Show Dialog" Click="Button_Click" />
</Window>
Remarks
You can improve reusability by moving the code implemented in MainWindow to an attached behavior.

Related

C# WPF Update two user controls using dependency property located in another project

I am trying to set a common dependency property to two different user controls in WPF.
I tried lots of solutions that I found but none of them worked.
So for the moment what I got is the following:
I have a class containing the common property which currently (after trying almost everything) looks this way:
namespace CommonProperties {
public class CommonProp : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty IsTrueProperty =
DependencyProperty.Register("IsTrue", typeof(bool), typeof(CommonProp), new PropertyMetadata(false));
private bool _isTrue;
public bool IsTrue
{
get { return (bool)GetValue(IsTrueProperty); }
set { SetValue(IsTrueProperty, value); NotifyPropertyChanged("IsTrue"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string nomPropriete)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(nomPropriete));
}
}}
I also have two user controls which looks like that: UC1:
<UserControl x:Class="ClassLibUC1.UC1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:CommonProperties;assembly=CommonProperties"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<TextBox x:Name="text1"
Width="254"
Height="23"
Margin="24,24,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding IsTrue}"
TextWrapping="Wrap" />
<Button Width="75"
Margin="70,68,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="Button_Click"
Content="Button" />
</Grid>
UC1 ViewModel:
namespace ClassLibUC1 {
public class ViewModelUC1 : INotifyPropertyChanged
{
CommonProp prop = new CommonProp();
public bool IsTrue
{
get { return prop.IsTrue; }
set { prop.IsTrue = value; NotifyPropertyChanged("IsTrue"); }
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Trigger the PropertyChanged event to update views.
/// </summary>
/// <param name="nomPropriete"></param>
public void NotifyPropertyChanged(string nomPropriete)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(nomPropriete));
}
}
UC1 Code:
public partial class UC1 : UserControl
{
ViewModelUC1 vm = new ViewModelUC1();
public UC1()
{
InitializeComponent();
this.DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
CommonProp prop = new CommonProp();
if (vm.IsTrue)
{
vm.IsTrue = false;
}
else
{
vm.IsTrue = true;
}
}
}
The second user control is exactly the same. The problem is that when I click a button in the first or the second user control, it only updates the selected control and not both of them.. Any idea how can I implement a common property for both controls?
With Dependency property we cannot achieve this because Dependency property belongs to one instance, means if you have two instance of user control, updating DependencyProperty value of one control will never update DependencyProperty of other Control as it is a different instance all together.
But still we can achieve that requirement of updating Same value for all usercontrols of same type like yours. For that we have to do some modification in xaml.cs file like below.
/// <summary>
/// Interaction logic for UC1.xaml
/// </summary>
public partial class UC1 : UserControl
{
private static List<UC1> _allInstanceOfThisControl = new List<UC1>();
ViewModelUC1 vm = new ViewModelUC1();
public UC1()
{
InitializeComponent();
this.DataContext = vm;
_allInstanceOfThisControl.Add(this);
this.Unloaded += UC1_Unloaded;
}
private void UC1_Unloaded(object sender, RoutedEventArgs e)
{
_allInstanceOfThisControl.Remove(this);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (vm.IsTrue)
{
vm.IsTrue = false;
}
else
{
vm.IsTrue = true;
}
var _allInstanceOfThisControlExceptthis = _allInstanceOfThisControl.Where(s => s != this).ToList();
_allInstanceOfThisControlExceptthis.ForEach(s =>
{
(s.DataContext as ViewModelUC1).IsTrue = vm.IsTrue;
});
}
Hope this will help to achieve your requirement.

WPF Custom Control rerendering on view model subproperties change

Can WPF Custom Control trace view model subproperty changes to automatically rerender itself?
Let’s say that I have a model with two properties:
public class FullName : ViewModel
{
string _first;
string _last;
public string First
{
get { return _first; }
set
{
_first = value;
RaisePropertyChanged();
}
}
public string Last
{
get { return _last; }
set
{
_last = value;
RaisePropertyChanged();
}
}
}
Where ViewModel is:
public abstract class ViewModel : INotifyPropertyChanged
{
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) =>
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) =>
PropertyChanged?.Invoke(this, e);
public event PropertyChangedEventHandler PropertyChanged;
}
I would like to have a Dependency Property on the WPF Custom Control (AffectsRender, no SubPropertiesDoNotAffectRender) to reference model in such a way that control automatically rerenders on First and Last property changes:
public class Tag : Control
{
public static readonly DependencyProperty FullNameProperty =
DependencyProperty.Register("FullName", typeof(FullName), typeof(Tag),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public FullName FullName
{
get { return (FullName)GetValue(FullNameProperty); }
set { SetValue(FullNameProperty, value); }
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (FullName == null)
return;
FontFamily courier = new FontFamily("Courier New");
Typeface courierTypeface = new Typeface(courier, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
FormattedText ft2 = new FormattedText(FullName.First + " " + FullName.Last,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
courierTypeface,
14.0,
Brushes.Black);
drawingContext.DrawText(ft2, new Point());
}
}
Here is the snippet to test it:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication3"
mc:Ignorable="d"
Title="MainWindow" Height="139.9" Width="249.514">
<StackPanel>
<StackPanel.DataContext>
<local:FullName>
<local:FullName.First>John</local:FullName.First>
<local:FullName.Last>Doe</local:FullName.Last>
</local:FullName>
</StackPanel.DataContext>
<local:Tag FullName="{Binding}" Height="20"/>
<TextBox Text="{Binding First, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Last, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Window>
Unfortunately, it does not work – changes are not propagating to the custom control. Can it be done efficiently? What this SubPropertiesDoNotAffectRender is about then?
For this to work, your FullName class has to be a Freezable and your First and Last properties have to be dependency properties.
You could take a look at the current implementation of the DependencyObject:
internal void NotifySubPropertyChange(DependencyProperty dp)
{
InvalidateSubProperty(dp);
// if the target is a Freezable, call FireChanged to kick off
// notifications to the Freezable's parent chain.
Freezable freezable = this as Freezable;
if (freezable != null)
{
freezable.FireChanged();
}
}
This mechanism was originally not intended for observing the sub-properties of a bound view-model. It helps to simplify the FrameworkElements measuring, arranging and rendering by observing the Freezables` properties and triggering appropriate actions on changing their properties and sub-properties.
You can find a nice blog post here that explains how does the retained graphics system in WPF work and how to use the feature you're interested in.

How to create command in usercontrol?

I'm developing my first application in WPF with the pattern MVC and I have a question.
I have created a usercontrol from type Grid to made a custom title bar. This grid contains a X button and I want to associate this button to a command.
My grid in XAML:
<Grid x:Class="Views.TitleBarView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Style="{DynamicResource TitleStyleGrid}"
x:Name="barView">
<Label x:Name="labelAppName" Style="{DynamicResource TitleStyleLabel}" Content="Title"/>
<Button x:Name="bttnClose" Style="{DynamicResource ButtonStyleCloseWindow}" Command="{Binding CloseCommand}"/>
<Button x:Name="buttonMinimize" Style="{DynamicResource ButtonStyleMinimizeWindow}" Command="{Binding MinimizeCommand}"/>
</Grid>
The C# view:
public partial class TitleBarView : Grid
{
public TitleBarView()
{
InitializeComponent();
TitleBarViewModel tvm = new TitleBarViewModel();
tvm.RequestClose += (s, e) => this.Close();
tvm.RequestMinimize+= (s, e) => this.Minimize();
DataContext = tvm;
}
private void Close()
{
Window.GetWindow(this).Close();
}
private void Minimize()
{
Application.Current.MainWindow.WindowState = System.Windows.WindowState.Minimized;
}
}
The C# viewModel:
public class TitleBarViewModel : ViewModelBase, IRequestMinimizeViewModel, IRequestCloseViewModel
{
private RelayCommand minimizeCommand;
protected RelayCommand closeCommand;
public event EventHandler RequestMinimize;
public event EventHandler RequestClose;
#region MinimizeCommand
public ICommand MinimizeCommand
{
get
{
if (minimizeCommand == null)
{
minimizeCommand = new RelayCommand(Minimize);
}
return minimizeCommand;
}
}
private void Minimize()
{
RequestMinimize(this, null);
}
#endregion
#region CloseCommand
public ICommand CloseCommand
{
get
{
if (closeCommand == null)
{
closeCommand = new RelayCommand(Close);
}
return closeCommand;
}
}
protected void Close()
{
RequestClose(this, null);
}
#endregion
}
I saw that it's not recommended to set a DataContext on a userControl. And when I do this, I can't change the close command. For example I want that when the main windows calls command close it calls Application.Current.Shutdown(); instead of Application.Current.Shutdown();
I know that I have something wrong but I'm too confuse to solve it. Can you explain me how to create command for userControl ? (Or just tell me what I'm doing wrong)
Thank you

MouseButtonEventArgs.MouseDevice.DirectlyOver misunderstanding

I was faced with the next misunderstanding.
Preamble:
I have wpf application with next essential UI parts: RadioButtons and some control that use dropdown based on Popup (in combobox manner). According to some logic every radiobutton hook PreviewMouseDown event and do some calculations.
In the next scenario,
User opens popup (do not select something, popup just staying open)
User click on radiobutton
PreviewMouseDown will not be fired for radiobutton as expected (because of Popup feature).
And my aim is firing PreviewMouseDown for RadioButton despite of one.
Attempts to solve:
Fast and dirty solution is: hook PreviewMouseDown for Popup and re-fire PreviewMouseDown event with new source if required, using radiobutton as source. New source can be obtained via MouseButtonEventArgs.MouseDevice.DirectlyOver. The next piece of code do that (event is re-fired only if Popup "eat" PreviewMouseDown for outer click):
private static void GrantedPopupPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var popup = sender as Popup;
if(popup == null)
return;
var realSource = e.MouseDevice.DirectlyOver as FrameworkElement;
if(realSource == null || !realSource.IsLoaded)
return;
var parent = LayoutTreeHelper.GetParent<Popup>(realSource);
if(parent == null || !Equals(parent, popup ))
{
e.Handled = true;
var args = new MouseButtonEventArgs(e.MouseDevice,
e.Timestamp,
e.ChangedButton)
{
RoutedEvent = UIElement.PreviewMouseDownEvent,
Source = e.MouseDevice.DirectlyOver,
};
realSource.RaiseEvent(args);
}
}
This works well when I'm attaching that handler to Popup.PreviewMouseDown directly via Behavior and do not work (PreviewMouseDown isn't fired for radiobutton) if I'm attaching one via EventManager.RegisterClassHandler (aim is to avoid attaching behavior to every Popup that can occure on page with these radiobuttons):
EventManager.RegisterClassHandler(
typeof (Popup),
PreviewMouseDownEvent,
new MouseButtonEventHandler(GrantedPopupPreviewMouseDown));
Debugger showed that e.MouseDevice.DirectlyOver (see code above) is Popup, not Radiobutton (as it is was when I've attached handler via Behavior)!
Question:
How and whyMouseButtonEventArgs can be different for the same action, if eventhandler attached in two different ways?
Can someone explaing this behavior?
Thanks a lot.
The combo box is provided as a way for users to select from a group of options, and you likely want to do that. But it also has other contracts. It says that the user should be focused on this and only this task. But that is not your situation. You want to show the options, have them hide able, and allow the user to do other things while they are shown.
I think instead of combo boxes you want some other control. My suggestion is to use an expander that contains a listbox. Given:
class NotificationObject : INotifyPropertyChanged
{
public void RaisePropertyChanged(string name)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
class ComboEntry : NotificationObject
{
public string Name { get; private set; }
private string _option = "Off";
public string Option
{
get { return _option; }
set { _option = value; RaisePropertyChanged("Option"); }
}
public ComboEntry()
{
Name = Guid.NewGuid().ToString();
}
}
class MyDataContext : NotificationObject
{
public ObservableCollection<ComboEntry> Entries { get; private set; }
private ComboEntry _selectedEntry;
public ComboEntry SelectedEntry
{
get { return _selectedEntry; }
set { _selectedEntry = value; RaisePropertyChanged("SelectedEntry"); }
}
public MyDataContext()
{
Entries = new ObservableCollection<ComboEntry>
{
new ComboEntry(),
new ComboEntry(),
new ComboEntry()
};
SelectedEntry = Entries.FirstOrDefault();
}
public void SetOption(string value)
{
Entries
.ToList()
.ForEach(entry => entry.Option = value);
}
}
I think you want the following XAML:
<Window x:Class="RadioInCombo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RadioInCombo"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MyDataContext x:Key="myDataContext" />
<DataTemplate x:Key="ComboEntryTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<Border Width="5" />
<TextBlock Text="{Binding Option}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel DataContext="{StaticResource myDataContext}">
<RadioButton x:Name="OnButton"
Content="On"
PreviewMouseDown="OnButton_PreviewMouseDown" />
<RadioButton x:Name="OffButton"
Content="Off"
PreviewMouseDown="OffButton_PreviewMouseDown" />
<Expander Header="{Binding SelectedEntry}"
HeaderTemplate="{StaticResource ComboEntryTemplate}">
<ListBox ItemsSource="{Binding Entries}"
ItemTemplate="{StaticResource ComboEntryTemplate}" />
</Expander>
</StackPanel>
</Window>
And the following code-behind:
private MyDataContext GetMyDataContext()
{
var candidate = FindResource("myDataContext") as MyDataContext;
if (candidate == null) throw new ApplicationException("Could not locate the myDataContext object");
return candidate;
}
private void OnButton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
GetMyDataContext().SetOption("On");
}
private void OffButton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
GetMyDataContext().SetOption("Off");
}

WPF DataBinding Issues - Possible Noob Problems

I am trying to bind a ViewModel property of type Visibility to the visibility property on a Dock Panel:
Updated ViewModel Code:
public class SelectWaferButtonViewModel : INotifyPropertyChanged
{
private bool isClicked;
public SelectWaferButtonViewModel()
{
isClicked = false;
}
public bool IsControlVisible
{
get
{
return isClicked;
}
set
{
isClicked = value;
OnPropertyChanged("IsControlVisible");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnButtonClick()
{
if (isClicked)
{
IsControlVisible = false;
}
else
{
IsControlVisible = true;
}
}
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
and here is my updated XAML code:
<DockPanel
Name="tvwDockPanel"
Width="200"
Visibility="{Binding IsControlVisible, FallbackValue=Collapsed, Converter={StaticResource BoolToVisConverter}}"
DockPanel.Dock="Left">
<DockPanel
DockPanel.Dock="Top"
Height="22">
</DockPanel>
and I set the data context in the code behind with this line:
tvwDockPanel.DataContext = btnSelectWaferViewModel;
where btnSelectWaferViewModel is the ViewModel object for this situation.
and for fun, here is my code behind:
public partial class WaferTrackerWindow : Window
{
List<ISubscribeEvents> subscriptionList;
SelectWaferButtonViewModel btnSelectWaferViewModel;
public WaferTrackerWindow()
{
InitializeComponent();
this.InstantiateObjects();
this.SubscribeEvents();
this.SetDataContexts();
}
#region Methods
private void SetDataContexts()
{
tvwDockPanel.DataContext = btnSelectWaferViewModel.IsControlVisible;
}
private void SubscribeEvents()
{
foreach (ISubscribeEvents subscriber in subscriptionList)
{
subscriber.SubscribeEvents();
}
}
private void InstantiateObjects()
{
btnSelectWaferViewModel = new SelectWaferButtonViewModel();
subscriptionList = new List<ISubscribeEvents>();
subscriptionList.Add(
new Classes.WaferTrackerWindow.SelectWaferButtonView(btnSelectWafer, btnSelectWaferViewModel));
}
#endregion
}
All I want to do click the button btnSelectWafer and have the tvwDockPanel's visibility property to get to set to Visible via binding. Then when you click again on btnSelectWafer, tvwDockPanel's visibility property gets set back to Collapsed again. tvwDockPanel's visibility will only ever be either Collapsed or Visible.
Any help would be awesome, I am rather new to this whole data binding concept.
You have several issues here:
First of all, the intent of MVVM (if you're trying to do this with MVVM) is to separate logic from presentation. This means that in no way your ViewModel can have a reference to System.Windows.Controls.Button, nor to System.Windows.Visibility, nor to any other classes inside the System.Windows Namespace.
It is not clear to me what your SelectWaferButtonViewModel class is doing with the Button, but you need to remove the Button from there.
Also, If you need to manipulate the Visibility of a control from the ViewModel layer, you'd better use a Boolean property and the BooleanToVisibilityConverter in XAML:
ViewModel:
public bool IsControlVisible {get;set;} //Don't forget INotifyPropertyChanged!!
XAML:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
</Window.Resources>
<DockPanel Visibility="{Binding IsControlVisible, Converter={StaticResource BoolToVisConverter}}"/>
The problem is that you're binding your DockPanel to the boolean property of your view model, and then setting the Visiblity property of your UI element to the IsControlVisible property of the datacontext (which doesn't exist).
Change to:
private void SetDataContexts()
{
tvwDockPanel.DataContext = btnSelectWaferViewModel;
}

Categories

Resources