I am trying to redirect the Window Closed event to my ViewModel, but lack the proper hands on experience with AttachedProperties.
The class that holds the AttachedProperty
public class WindowClosedBehavior
{
public static readonly DependencyProperty ClosedProperty = DependencyProperty.RegisterAttached(
"Closed",
typeof (ICommand),
typeof (WindowClosedBehavior),
new UIPropertyMetadata(ClosedChanged));
private static void ClosedChanged(
DependencyObject target,
DependencyPropertyChangedEventArgs e)
{
var window = target as Window;
if (window != null)
{
// ??
}
}
public static void SetClosed(Window target, ICommand value)
{
target.SetValue(ClosedProperty, value);
}
}
How can I implement the behavior so that it will close the window and trigger the RelayCommand?
The (stripped) ViewModel :
public RelayCommand WindowClosedCommand { get; private set; }
public MainCommandsViewModel()
{
WindowClosedCommand = new RelayCommand(WindowClosedCommandOnExecuted, WindowClosedCommandOnCanExecute);
}
MainWindow.xaml
<Window x:Class="TvShowManager.UserInterface.Views.MainWindow"
<!-- left out irrelevant parts -->
xmlns:closeBehaviors="clr-namespace:TvShowManager.UserInterface.CloseBehaviors"
closeBehaviors:WindowClosedBehavior.Closed="{Binding WindowCloseCommand}" >
I simply bind a RelayCommand (WindowCloseCommand) to the attached property.
I tried debugging through this to get better understanding and hopefully figure out how to proceed, but no breakpoints are being hit in the class that holds my attached property. If anybody can explain why my code in WindowClosedBehavior never gets executed I would also greatly appreciate the advice there.
I hope it's clear what I am trying to achieve and that somebody can help me out.
Many thanks
Within the ClosedChanged callback, just store the command and register an event handler to the window's Closed event to invoke the command:
private static ICommand _command;
private static void ClosedChanged(
DependencyObject target,
DependencyPropertyChangedEventArgs e)
{
var window = target as Window;
if (window != null)
{
_command = e.NewValue as ICommand;
window.Closed += (sender, args) =>
{
if (_command != null)
_command.Execute(null);
}
}
}
In addition, you might want to un-register all previously existing event handlers on the window's Closed event, but that is only necessary if you plan to change the WindowClosedBenahior during runtime.
Related
Preamble
There are dozens of simular Questions to this topic on StackOverflow already and I browsed a lot without finding a suitable answer, that would apply to my problem.
Task
I have a WPF Window in a MVVM pattern, which has quite a lot of buttons that open other windows. I'd like to have most of my windows appear in relation to my buttons (I have a toolbar in the top right corner of my MainWindow and want most of the smaller windows to appear right below my buttons), or at least on the same screen as my MainWindow.
Problem
At first, I thought this wasn't such a big deal and there were plenty of blogs and questions on google to this topic, yet all of them won't work on my project.
I am using the MVVM pattern, which means:
I can't use Mouse.GetPosition(ButtonName) on my Buttons, as the ViewModel doesn't know their names
I can't use Mouse.GetPosition(sender) in a Click-Event, as most Buttons use commands.
I also apparently can't use PointToScreen in my view's code behind, as it will cause an exception (this visual object is not connected to a \"PresentationSource\")
I could use Mouse.GetPosition(this) on a MouseMove-Event in my view's code behind and hand it down to my ViewModel, which will update a Property, that I can use in my Commands when creating the window, but I don't like the idea of having to update a property permanently. Also without PointToScreen I can't set the point in relation to my screen.
I can't use any WinForms references, as this would cause conflicts in my current project
Additional to Buttons, I also host a UserControl with Hyperlinks in my MainWindow, which open additional windows, that should be in relation to the hyperlinks.
Research
there are quite a few different answers to a question here, but none of them did work for me.
As my ViewModel doesn't know the XAML elements I can't simply access by point notation as suggested here
My ViewModel doesn't know a WorkingArea, so I couldn't even get my window to appear on the same screen as my MainWindow as demonstrated here
As most of the other answers, this one seems like it won't work in a ViewModel
Question
I've spent quite some time on a problem, that rather seemed trivial at first, already. Since most questions I've viewed so far seem to target windows without MVVM, what would be the proper approach in a ViewModel to set the location of a window to either my mouse coordinates or the coordinates of a clicked button?
edit:
MouseDownEvent as requested in Comments:
Xaml:
<Window x:Class="MySampleProject.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:MySampleProject"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
MouseDown="Window_MouseDown">
C#:
private void Window_MouseDown(object sender, MouseEventArgs e)
{
if (m_oDataContext != null)
{
m_oDataContext.MouseTest(Mouse.GetPosition(this));
}
}
oDataContext is my ViewModel. My MouseTest() is currently empty. I did set a breakpoint at the first bracket. The breakpoint is only reached when left-clicking within my window, not within one of its hosted controls.
Here comes an example of how you can pass a parameter to Command in your Vm:
Window Class:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext= new MyVm();
}
private void BtnWin1_OnClick(object sender, RoutedEventArgs e)
{
var dataContext = DataContext as MyVm;
var relativePoint = ((Button)sender).TransformToAncestor(this).Transform(new Point(0, 0));
relativePoint.X += this.Left;
relativePoint.Y += this.Top;
dataContext?.OpenWindow1Command.Execute(relativePoint);
}
private void BtnWin2_OnClick(object sender, RoutedEventArgs e)
{
var dataContext = DataContext as MyVm;
var relativePoint = ((Button)sender).TransformToAncestor(this).Transform(new Point(0, 0));
relativePoint.X += this.Left;
relativePoint.Y += this.Top;
dataContext?.OpenWindow2Command.Execute(relativePoint);
}
}
VM Class:
public class MyVm : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ICommand OpenWindow1Command { get; }
public ICommand OpenWindow2Command { get; }
public MyVm()
{
OpenWindow1Command = new RelayCommand(OpenWindow1Command_Execute);
OpenWindow2Command = new RelayCommand(OpenWindow2Command_Execute);
}
void OpenWindow1Command_Execute(object parameter)
{
var point = (Point)parameter;
var win1 = new Window1{WindowStartupLocation = WindowStartupLocation.Manual, Left = point.X, Top = point.Y};
win1.Show();
}
void OpenWindow2Command_Execute(object parameter)
{
var point = (Point)parameter;
var win2 = new Window2 { WindowStartupLocation = WindowStartupLocation.Manual, Left = point.X, Top = point.Y };
win2.Show();
}
}
And Relay class if you haven't implemented that:
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action<object> execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute.Invoke();
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
You will loose the CanExecute functionality of the Command with this approach, but will do the work.
I have created the following custom Dependency Property in the code behind.
This Dependency property of type infragistics XamDataGrid and so the owner.
I'm trying to get a reference of the grid through this property.
The following code compiles with no errors or warnings. However, the Dependency Property does not show in the XAML intelliSense.
I have tried typing the full name as well. It is not recognizing this DP.
I have cleaned the project and Rebuilt it.
I have even closed Visual Studio and reopened it.
using System.Windows;
using Infragistics.Windows.DataPresenter;
namespace Demo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public static readonly DependencyProperty DPRProperty =
DependencyProperty.Register(
"DPR",
typeof(XamDataGrid),
typeof(MainWindow),
new FrameworkPropertyMetadata(null));
public XamDataGrid DPR
{
get { return (XamDataGrid)GetValue(DPRProperty); }
set { SetValue(DPRProperty, value); }
}
}
}
The problem is that you expect the Dependency Property to show up in your base classes XAML "code"
however, you did not created a DP for Window but for MainWindow
If you would use a <MainWindow /> tag, it would show up
if you could explain what you try to archive, one may could help you further
edit
Think i understood now what your goal is
To get a reference to any object, you do not need a DP but rather have to order everything correctly
things required:
The Window (obviously)
A DataContext
Some Code-Behind
An Attached-Property
The Attached Property is looking pretty much like this
public class Initialized
{
public static DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand),
typeof(Initialized),
new UIPropertyMetadata(CommandChanged));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter",
typeof(object),
typeof(Initialized),
new UIPropertyMetadata(null));
public static void SetCommand(DependencyObject target, ICommand value)
{
target.SetValue(CommandProperty, value);
}
public static void SetCommandParameter(DependencyObject target, object value)
{
target.SetValue(CommandParameterProperty, value);
}
public static object GetCommandParameter(DependencyObject target)
{
return target.GetValue(CommandParameterProperty);
}
private static void CommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var type = target.GetType();
var ev = type.GetEvent("Initialized");
var method = typeof(Initialized).GetMethod("OnInitialized");
if ((e.NewValue != null) && (e.OldValue == null))
{
ev.AddEventHandler(target, Delegate.CreateDelegate(ev.EventHandlerType, method));
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
ev.RemoveEventHandler(target, Delegate.CreateDelegate(ev.EventHandlerType, method));
}
}
public static void OnInitialized(object sender, EventArgs e)
{
var control = sender as FrameworkElement;
var command = (ICommand)control.GetValue(CommandProperty);
var commandParameter = control.GetValue(CommandParameterProperty);
command.Execute(commandParameter);
}
}
In your DataContext (lets name it MainWindowDataContext), create a new ICommand property (Lets name it CmdInitialized) that puts the parameter passed to the command into your instance variable
Then, in your XAML code you just use the AttachedProperty like usual
<!-- `ev:` is the XAML namespace the Initialized class is located in -->
ev:Initialized.Command="{Binding CmdInitialized}"
ev:Initialized.CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
the important part however is: the DataContext needs to be already attached in the window BEFORE the compontents are initialized
this means that you have to edit your window code to something like this:
public MainWindow()
{
this.DataContext = new MainWindowDataContext();
InitializeComponent();
}
Afterwards, you should be fine
if you need solutions for the ICommand, search for RelayCommand on google :)
Is it possible to expose a public event from my ViewModel is such a way as to allow it to be bound to a custom DependencyProperty in my View?
My application is written in C# using the .NET 4.5 framework. It has a MVVM architecture with no code-behind in the view and custom DependencyProperty classes to bind WPF-specific behvaiours of the View to properties exposed by the ViewModel.
There is a set of properties that I would like the ViewModel to be able to expose that represent events to which the View needs to respond. For example, when a top level ViewModel object is about to be Disposed I would like the WPF View implementation to respond by closing the corresponding Window. This could occur when a configuration process has displayed a Dialog Window, the user has enetered and confirmed the information and the ViewModel has passed it to the Model and is no longer required.
I am aware that there are many questions that are specific to solving the 'show dialog from ViewModel' question; this is not one of them and I have a solution to that one.
I've read through the MSDN documentation for DependencyProperties and can't find anything specific to binding to event properties.
What I would like to achieve is something similar to the code below. This code builds, but results in a typical System.Windows.Data Error: 40 : BindingExpression path error: 'RequestCloseEvent' property not found error when the MainWindow is shown.
I am aware that there are many questions that go along the lines of 'please help me debug my System.Windows.Data Error: 40 issue'; this is (probably) not one of these either. (But I'd be happy if that's all it really is.)
Source for the custom DependencyProperty in WindowBindableProperties.cs:
using System;
using System.Threading;
using System.Windows;
namespace WpfEventBinding
{
public static class WindowBindableProperties
{
#region ViewModelTerminatingEventProperty
/// <summary>
/// Register the ViewModelTerminatingEvent custom DependencyProperty.
/// </summary>
private static DependencyProperty _viewModelTerminatingEventProperty =
DependencyProperty.RegisterAttached
(
"ViewModelTerminatingEvent",
typeof(ViewModelTerminatingEventHandler),
typeof(WindowBindableProperties),
new PropertyMetadata(null, ViewModelTerminatingEventPropertyChanged)
);
/// <summary>
/// Identifies the ViewModelTerminatingEvent dependency property.
/// </summary>
public static DependencyProperty ViewModelTerminatingEventProperty
{ get { return _viewModelTerminatingEventProperty; } }
/// <summary>
/// Gets the attached ViewModelTerminatingEvent dependecy property.
/// </summary>
/// <param name="dependencyObject">The window attached to the WindowViewModel.</param>
/// <returns>The ViewModelTerminatingEventHandler bound to this property</returns>
public static ViewModelTerminatingEventHandler GetViewModelTerminatingEvent
(DependencyObject dependencyObject)
{
return (dependencyObject.GetValue(ViewModelTerminatingEventProperty)
as ViewModelTerminatingEventHandler);
}
/// <summary>
/// Sets the ViewModelTerminatingEvent dependency property.
/// </summary>
public static void SetViewModelTerminatingEvent(
DependencyObject dependencyObject,
ViewModelTerminatingEventHandler value)
{
dependencyObject.SetValue(ViewModelTerminatingEventProperty, value);
}
/// <summary>
/// Gets the ViewModelTerminatingEvent dependency property.
/// </summary>
private static void ViewModelTerminatingEventPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Window instance = d as Window;
if (null != instance)
{
if (null != e.OldValue)
{
throw new System.InvalidOperationException(
"ViewModelTerminatingEvent dependency property cannot be changed.");
}
if (null != e.NewValue)
{
// Attach the Window.Close() method to the ViewModel's event
var newEvent = (e.NewValue as ViewModelTerminatingEventHandler);
newEvent += new ViewModelTerminatingEventHandler(() => instance.Close());
}
}
}
#endregion
}
}
Source for MainWindow.xaml:
(This example contains code-behind to simplify the Stop Button implementation.)
<Window x:Class="WpfEventBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:WpfEventBinding"
v:WindowBindableProperties.ViewModelTerminatingEvent="{Binding Path=RequestCloseEvent}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="{Binding Path=CloseCommandName}" Click="StopButton_Click" ></Button>
</Grid>
</Window>
Source for MainWindow.xaml.cs (code behind):
using System.Windows;
namespace WpfEventBinding
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void StopButton_Click(object sender, RoutedEventArgs e)
{
MainWindowViewModel vm = (DataContext as MainWindowViewModel);
if (null != vm)
{
vm.Stop();
}
}
}
}
Source for the MainWindowViewModel.cs:
using System;
using System.ComponentModel;
namespace WpfEventBinding
{
public delegate void ViewModelTerminatingEventHandler();
class MainWindowViewModel
: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// Raised by the ViewModel to indicate to the view that it is no longer required.
// Causes System.Windows.Data Error: 40 : BindingExpression path error. Is it
// Possible to bind to an 'event' property?
public event ViewModelTerminatingEventHandler RequestCloseEvent;
// This has to have the public 'get' to allow binding. Is there some way to
// do the same thing for the 'event'?
public String CloseCommandName { get; private set; }
public MainWindowViewModel()
{
CloseCommandName = "Close";
}
internal void Stop()
{
ViewModelTerminatingEventHandler RaiseRequestCloseEvent =
RequestCloseEvent;
if (null != RaiseRequestCloseEvent)
{
RaiseRequestCloseEvent();
}
}
internal void Start()
{
OnPropertyChanged("CloseCommandName");
OnPropertyChanged("ViewModelTerminatingEvent");
}
private void OnPropertyChanged(String propertyName)
{
PropertyChangedEventHandler RaisePropertyChangedEvent = PropertyChanged;
if (RaisePropertyChangedEvent != null)
{
var propertyChangedEventArgs = new PropertyChangedEventArgs(propertyName);
RaisePropertyChangedEvent(this, propertyChangedEventArgs);
}
}
}
}
Source for App.xaml:
<Application x:Class="WpfEventBinding.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Application.Resources>
<!-- Nothing to see here. Move along... -->
</Application.Resources>
</Application>
Source for App.xaml.cs
using System.Windows;
namespace WpfEventBinding
{
public partial class App : Application
{
public App()
{
Startup += new StartupEventHandler(App_Startup);
}
void App_Startup(object sender, StartupEventArgs e)
{
MainWindowViewModel vm = new MainWindowViewModel();
MainWindow window = new MainWindow();
// Make sure this is set before attempting binding!
window.DataContext = vm;
vm.Start();
window.Show();
}
}
}
It appears that the public event ViewModelTerminatingEventHandler RequestCloseEvent; syntax is not sufficient to allo the data binding to occur. A similar problem is see if the public String CloseCommandName { get; private set; } is declared as public String CloseCommandName; without the { get; private set; }. However, there is no { get; private set; } for events, which use the {add{} remove{}} syntax (and that does not solve the problem either).
Is what I'm attempting possible and if so, what have I missed?
View closing means window closing event. So you basically want react on events in the view. I read recently this arcticle, there was a very good image
and also mentioned EventBehavior existence.
Your best bet, if you don't want any code behind, is to use behaviors. Behavior is a simple attached property, which can perform actions, to example rising application-wide commands, which ViewModel can then catch without MVVM issues.
Here is an example of behavior:
public static class FreezeBehavior
{
public static bool GetIsFrozen(DependencyObject obj)
{
return (bool)obj.GetValue(IsFrozenProperty);
}
public static void SetIsFrozen(DependencyObject obj, bool value)
{
obj.SetValue(IsFrozenProperty, value);
}
public static readonly DependencyProperty IsFrozenProperty =
DependencyProperty.RegisterAttached("IsFrozen", typeof(bool), typeof(FreezeBehavior), new PropertyMetadata(OnIsFrozenChanged));
private static void OnIsFrozenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
var freezable = d as Freezable;
if (freezable != null && freezable.CanFreeze)
freezable.Freeze();
}
}
}
it's used like this
<DropShadowEffect ShadowDepth="2" local:FreezeBehavior.IsFrozen="True"/>
It can be attached to any freezable to freeze it. In your case you want to subscribe to event and invoke command or set property, or whatever to inform ViewModel.
What you are asking for is kinda weird, but I'm not going to get into a big long discussion about that....
You don't bind to events - you expose them and the view can add handlers for the events.
Of course this means you will have to put some code behind into the view - but this is fine provided it is UI related. To complete the decoupling your view should only handle the viewmodel as an interface, this means you can easily swap out viewmodels at a later stage.
(Note that I've avoided talking about event triggers).
I have a complex Plot RenderingControl that I have placed into a View. What would be the ideal way to handle zooming with respect to the MVVM pattern? I want the user to be able to zoom by clicking and dragging on the plot.
One approach I see would be to take the MouseMove, MouseUp, MouseDown events of the Plot control and wire them up to commands in the PlotViewModel. Now in response to the commands the ViewModel could update it's ZoomLevel property, which could be bound to the view, and cause the View to zoom in. While the user is clicking and dragging I would also like to display a rectangle indicating the region that will be zoomed. Would it make sense to keep an AnnotationViewModel in PlotViewModel for the zoom preview?
Another approach would be to handle it all in the View and not involve the ViewModel at all.
The main difference I see is that capturing the behavior in the ViewModel will make that behavior much more re-useable than in the View. Though I have a feeling that the underlying Plot control and the resulting View are complex enough that there isn't going to be much of chance for re-use anyway. What do you think?
I think there are several ways to solve your problem. HighCore right, when he says that Zoom applies to View, so it is advisable to leave it on the side View. But there are alternatives, we consider them below. Unfortunately, I did not deal with Plot RenderingControl, so I will describe a solution based on an abstract, independent of the control.
AttachedBehavior
In this case, I would have tried to identify possible all the work with the control via an attached behavior, it is ideally suited for the MVVM pattern, and it can be used in the Blend.
Example of work
In your View, control is defined and an attached behavior, like so:
<RenderingControl Name="MyPlotControl"
AttachedBehaviors:ZoomBehavior.IsStart="True" ... />
And in code-behind:
public static class ZoomBehavior
{
public static readonly DependencyProperty IsStartProperty;
public static void SetIsStart(DependencyObject DepObject, bool value)
{
DepObject.SetValue(IsStartProperty, value);
}
public static bool GetIsStart(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsStartProperty);
}
static ZoomBehavior()
{
IsStartMoveProperty = DependencyProperty.RegisterAttached("IsStart",
typeof(bool),
typeof(ZoomBehavior),
new UIPropertyMetadata(false, IsStart));
}
private static void IsStart(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
UIElement uiElement = sender as UIElement;
if (uiElement != null)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
uiElement.MouseDown += new MouseButtonEventHandler(ObjectMouseDown);
uiElement.MouseMove += new MouseEventHandler(ObjectMouseMove);
uiElement.MouseUp += new MouseButtonEventHandler(ObjectMouseUp);
}
}
}
// Below is event handlers
}
Once you're set to true for property IsStart, PropertyChanged handler is triggered and it set the handlers for events that contain the basic logic.
For the transmission of additional data in you behavior register additional dependency properties, for example:
<RenderingControl Name="MyPlotControl"
AttachedBehaviors:ZoomBehavior.IsStart="True"
AttachedBehaviors:ZoomBehavior.ZoomValue="50" />
In code-behind:
// ... Here registered property
public static void SetZoomValue(DependencyObject DepObject, int value)
{
DepObject.SetValue(ZoomValueProperty, value);
}
public static int GetZoomValue(DependencyObject DepObject)
{
return (int)DepObject.GetValue(ZoomValueProperty);
}
// ... Somewhere in handler
int value = GetZoomValue(plotControl);
To retrieve data on the behavior, I use a singleton pattern. This pattern represents global static access point to the object and must guarantee the existence of a single instance of the class.
Example of using this pattern (taken from the behavior, who worked with the time display in the View):
public class TimeBehavior : INotifyPropertyChanged
{
// Global instance
private static TimeBehavior _instance = new TimeBehavior();
public static TimeBehavior Instance
{
get
{
return _instance;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private string _currentTime = DateTime.Now.ToString("HH:mm");
public string CurrentTime
{
get
{
return _currentTime;
}
set
{
if (_currentTime != value)
{
_currentTime = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CurrentTime"));
}
}
}
}
private string _currentDayString = ReturnDayString();
public string CurrentDayString
{
get
{
return _currentDayString;
}
set
{
if (_currentDayString != value)
{
_currentDayString = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CurrentDayString"));
}
}
}
}
private string _currentMonthAndDayNumber = ReturnMonthAndDayNumber();
public string CurrentMonthAndDayNumber
{
get
{
return _currentMonthAndDayNumber;
}
set
{
if (_currentMonthAndDayNumber != value)
{
_currentMonthAndDayNumber = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CurrentMonthAndDayNumber"));
}
}
}
}
public static readonly DependencyProperty IsTimerStartProperty;
public static void SetIsTimerStart(DependencyObject DepObject, bool value)
{
DepObject.SetValue(IsTimerStartProperty, value);
}
public static bool GetIsTimerStart(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsTimerStartProperty);
}
static void OnIsTimerStartPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(1000);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
}
static TimeBehavior()
{
IsTimerStartProperty = DependencyProperty.RegisterAttached("IsTimerStart",
typeof(bool),
typeof(TimeBehavior),
new PropertyMetadata(new PropertyChangedCallback(OnIsTimerStartPropertyChanged)));
}
private static void timer_Tick(object sender, EventArgs e)
{
_instance.CurrentTime = DateTime.Now.ToString("HH:mm");
_instance.CurrentDayString = ReturnDayString();
_instance.CurrentMonthAndDayNumber = ReturnMonthAndDayNumber();
}
}
Access to data in the View:
<TextBlock Name="WidgetTimeTextBlock"
Text="{Binding Path=CurrentTime,
Source={x:Static Member=AttachedBehaviors:TimeBehavior.Instance}}" />
Alternatives
Work in View via Interface
The point of this way is that we call a method in View via ViewModel, which does all the work, and he does not know about the View. This is accomplished by the operation of the interface and the well described here:
Talk to View
Using ServiceLocator
ServiceLocator allows you to work in the ViewModel, without violating the principles of MVVM. You have a RegisterService method where you register the instance of the service you want to provide and a GetService method which you would use to get the service you want.
More information can be found here:
Service Locator in MVVM
I have a window that essentially runs a timer. When the timer hits 0 I want to bring the window to the front so that it is visible and not hidden behind some other application.
From what I can gather I would simply call window.activate() to accomplish this but with mvvm my view model doesn't have a reference to window.
A "purist" MVVM solution is to use a behavior. Below is a behavior for a Window with an Activated property. Setting the property to true will activate the window (and restore it if it is minimized):
public class ActivateBehavior : Behavior<Window> {
Boolean isActivated;
public static readonly DependencyProperty ActivatedProperty =
DependencyProperty.Register(
"Activated",
typeof(Boolean),
typeof(ActivateBehavior),
new PropertyMetadata(OnActivatedChanged)
);
public Boolean Activated {
get { return (Boolean) GetValue(ActivatedProperty); }
set { SetValue(ActivatedProperty, value); }
}
static void OnActivatedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) {
var behavior = (ActivateBehavior) dependencyObject;
if (!behavior.Activated || behavior.isActivated)
return;
// The Activated property is set to true but the Activated event (tracked by the
// isActivated field) hasn't been fired. Go ahead and activate the window.
if (behavior.AssociatedObject.WindowState == WindowState.Minimized)
behavior.AssociatedObject.WindowState = WindowState.Normal;
behavior.AssociatedObject.Activate();
}
protected override void OnAttached() {
AssociatedObject.Activated += OnActivated;
AssociatedObject.Deactivated += OnDeactivated;
}
protected override void OnDetaching() {
AssociatedObject.Activated -= OnActivated;
AssociatedObject.Deactivated -= OnDeactivated;
}
void OnActivated(Object sender, EventArgs eventArgs) {
this.isActivated = true;
Activated = true;
}
void OnDeactivated(Object sender, EventArgs eventArgs) {
this.isActivated = false;
Activated = false;
}
}
The behavior requires a reference to System.Windows.Interactivity.dll. Fortunately, this is now available on NuGet in the Blend.Interactivity.Wpf package.
The behavior is attached to a Window in XAML like this:
<Window ...>
<i:Interaction.Behaviors>
<Behaviors:ActivateBehavior Activated="{Binding Activated, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
The view-model should expose a boolean Activated property. Setting this property to true will activate the window (unless it is already activated). As an added bonus it will also restore a minimized window.
You could go about it in a couple of ways - adding a reference to the window could work since the viewmodel is not coupled with the view but related to it, but I don't really like that approach since it pretty much does couple your view to your viewmodel - which is not really the point of MVVM
A better approach may be to have your viewmodel raise an event or a command which the view can handle. This way the view gets to decide what UI action is associated with the command/event
e.g. simply
class SomeView
{
void HandleSomeCommandOrEvent()
{
this.Activate();
}
}
Of course how you wire this up is up to you but I'd probably try and get routed commands happening
Edit: You can't really 'bind' a simple event, since it's invoked from the viewmodel.
A simple event based example is just to add the event to the viewmodel and handle it directly ... e.g. imagine the following MainWindow with a ViewModel property
public partial class MainWindow : Window
{
MainWindowViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
ViewModel.ShowMessage += ViewModel_ShowMessage;
this.DataContext = ViewModel;
}
void ViewModel_ShowMessage(object sender, ShowMessageEventArgs e)
{
MessageBox.Show(e.Message, "Some caption", MessageBoxButton.OK);
}
}
Then the ViewModel can just fire the event:
// The view model
public class MainWindowViewModel
{
// The button click command
public RelayCommand ButtonClickCommand { get; set; }
// The event to fire
public event EventHandler<ShowMessageEventArgs> ShowMessage;
public MainWindowViewModel()
{
ButtonClickCommand = new RelayCommand(ButtonClicked);
}
void ButtonClicked(object param)
{
// This button is wired up in the view as normal and fires the event
OnShowMessage("You clicked the button");
}
// Fire the event - it's up to the view to decide how to implement this event and show a message
void OnShowMessage(string message)
{
if (ShowMessage != null) ShowMessage(this, new ShowMessageEventArgs(message));
}
}
public class ShowMessageEventArgs : EventArgs
{
public string Message { get; private set; }
public ShowMessageEventArgs(string message)
{
Message = message;
}
}
The XAML would be:
<Button Command="{Binding ButtonClickCommand}">Click me!</Button>
So the button invokes the command, which in turn fires the event which the view (MainWindow) handles and shows a messagebox. This way the view/UI decides on the course of action based on the type of event raised. Of course it could be your timer which fired the event
You can always go down the more involved route such as some of the answers on this question...
How should the ViewModel close the form?
but to be honest, it depends if you really need it - a simple event works well - some people overcomplicate things for the sake of elegance, but at the detriment of simplicity and productivity!
I would go this way:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
// View
public partial class TestActivateWindow : Window
{
public TestActivateWindow() {
InitializeComponent();
Messenger.Default.Register<ActivateWindowMsg>(this, (msg) => Activate());
}
}
// View Model
public class MainViewModel: ViewModelBase
{
ICommand _activateChildWindowCommand;
public ICommand ActivateChildWindowCommand {
get {
return _activateChildWindowCommand?? (_activateChildWindowCommand = new RelayCommand(() => {
Messenger.Default.Send(new ActivateWindowMsg());
}));
}
}
}
public class ActivateWindowMsg
{
}