Creating a Window like so, using my custom UserControl as the content:
Window newCacheForm = new Window
{
Title = "Add New Cache Tag",
Content = new NewCacheControl()
};
I want to open the Window as a dialog and get the result:
var result = newCacheForm.ShowDialog();
I have the code in place to bind and set the dialog to true or false, but how do I close the Window from the UserControl ViewModel? If that can't be done, how do I work this in an MVVM friendly way?
In this case I would use an attached behavior, it allows using independent logic on the side of View. I personally did not create it, but took here and little supplemented - added Get() to a dependency property.
Below as a full code of this behavior:
public static class WindowCloseBehaviour
{
public static bool GetClose(DependencyObject target)
{
return (bool)target.GetValue(CloseProperty);
}
public static void SetClose(DependencyObject target, bool value)
{
target.SetValue(CloseProperty, value);
}
public static readonly DependencyProperty CloseProperty = DependencyProperty.RegisterAttached("Close",
typeof(bool),
typeof(WindowCloseBehaviour),
new UIPropertyMetadata(false, OnClose));
private static void OnClose(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && ((bool)e.NewValue))
{
Window window = GetWindow(sender);
if (window != null)
window.Close();
}
}
private static Window GetWindow(DependencyObject sender)
{
Window window = null;
if (sender is Window)
window = (Window)sender;
if (window == null)
window = Window.GetWindow(sender);
return window;
}
}
In the Click handler of creating new Window I added this behavior like this:
private void Create_Click(object sender, RoutedEventArgs e)
{
Window newCacheForm = new Window
{
Title = "Add New Cache Tag",
Content = new TestUserControl(),
DataContext = new TestModel() // set the DataContext
};
var myBinding = new Binding(); // create a Binding
myBinding.Path = new PropertyPath("IsClose"); // with property IsClose from DataContext
newCacheForm.SetBinding(WindowCloseBehaviour.CloseProperty, myBinding); // for attached behavior
var result = newCacheForm.ShowDialog();
if (result == false)
{
MessageBox.Show("Close Window!");
}
}
And in the Close handler of UserControl write this:
private void Close_Click(object sender, RoutedEventArgs e)
{
TestModel testModel = this.DataContext as TestModel;
testModel.IsClose = true;
}
Naturally, instead of Click handlers for the Buttons should be used the commands.
The entire project is available here.
Related
I'm trying to create something like expandable button - button with context menu above, which will open by left mouse button click. Only thing I still can't finishing up is items property, which could be setting up in WPF XAML designer like for ContextMenu control. So, as far as I can understand, that means I need to use ItemCollection type for my property. Ok. Let's take a look on my component:
public partial class MenuButton : Button
{
private readonly ContextMenu Menu;
public static readonly DependencyProperty MenuItemsProperty =
DependencyProperty.Register("MenuItems", typeof(ItemCollection), typeof(ContextMenu), new PropertyMetadata(default(ItemCollection), new PropertyChangedCallback(OnSomeMenuItemsPropertyChanged)));
public ItemCollection MenuItems
{
get => (ItemCollection)GetValue(MenuItemsProperty);
set => SetValue(MenuItemsProperty, value);
}
public MenuButton()
{
MenuItems = new DataGrid().Items; // I really need to do it - otherwise I'll get an error
Menu = new ContextMenu
{
HasDropShadow = false,
PlacementTarget = this,
Placement = PlacementMode.Top,
};
InitializeComponent();
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
Menu.Width = ActualWidth;
Menu.IsOpen = true;
}
private static void OnSomeMenuItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
if (target is ContextMenu menu)
{
menu.ItemsSource = (ItemCollection)e.NewValue;
}
}
}
But I can't 'catch' the OnSomeMenuItemsPropertyChanged - breakpoint does not work here. So, that means this mechanism is wrong.
How can I fix that? Should I use OnItemsChanged and OnItemsCollectionChanged events instead (like for ObservableCollection) to handle the changes of the property? Or maybe something else?
So, finally I found the solution. I don't claim that this is the best practice, but maybe it will be useful for someone:
public partial class MenuButton : Button
{
private readonly ContextMenu Menu;
public static readonly DependencyProperty MenuItemsProperty =
DependencyProperty.Register("MenuItems", typeof(ItemCollection), typeof(MenuButton), new PropertyMetadata(default(ItemCollection), new PropertyChangedCallback(OnSomeMenuItemsPropertyChanged)));
public ItemCollection MenuItems
{
get => (ItemCollection)GetValue(MenuItemsProperty);
set => SetValue(MenuItemsProperty, value);
}
public MenuButton()
{
Menu = new ContextMenu
{
HasDropShadow = false,
PlacementTarget = this,
Placement = PlacementMode.Top,
};
MenuItems = new DataGrid().Items;
InitializeComponent();
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
Menu.Width = ActualWidth;
Menu.IsOpen = true;
}
private static void OnSomeMenuItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var menuButton = target as MenuButton;
menuButton.Menu.ItemsSource = (ItemCollection)e.NewValue;
}
}
I'm using C#, WPF, ReactiveUI and Prism to create an application with many different views (user controls). On some views there are buttons/menu items that bind to a command in the view model. I would like these buttons to also activate using a key combination such as ctrl+s, etc....
What I've tried
InputBindings but that only works when the view that defines these input bindings has focus.
ApplicationCommands the predefined commands like ApplicationCommands.Close seem useful. I can reference them both in the view and the view model, but I don't know how subscribe to them in my view model. It also seems that I have to 'activate' the command first, or at least change CanExecute since any button bound to such command stays disabled.
What I wish for
Let's say I have a view that represents the top menu bar MenuView with a button myButton and a corresponding view model MenuViewModel with a command myCommand. I would like to bind myButton to myCommand and the keyboard shortcut ctrl+u to myCommand without MenuView knowing about the implementation of its view model. The keyboard shortcut should work as long as the window that contains MenuView has focus.
I don't really care if the keyboard short-cut is either in the view or view model.
You could create an attached Blend behaviour that handles the PreviewKeyDown event of the parent window:
public class KeyboardShortcutBehavior : Behavior<FrameworkElement>
{
private Window _parentWindow;
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(nameof(Command), typeof(ICommand),
typeof(KeyboardShortcutBehavior), new FrameworkPropertyMetadata(null));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty ModifierKeyProperty =
DependencyProperty.Register(nameof(ModifierKey), typeof(ModifierKeys),
typeof(KeyboardShortcutBehavior), new FrameworkPropertyMetadata(ModifierKeys.None));
public ModifierKeys ModifierKey
{
get { return (ModifierKeys)GetValue(ModifierKeyProperty); }
set { SetValue(ModifierKeyProperty, value); }
}
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register(nameof(Key), typeof(Key),
typeof(KeyboardShortcutBehavior), new FrameworkPropertyMetadata(Key.None));
public Key Key
{
get { return (Key)GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObject_Loaded;
AssociatedObject.Unloaded += AssociatedObject_Unloaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
_parentWindow = Window.GetWindow(AssociatedObject);
if(_parentWindow != null)
{
_parentWindow.PreviewKeyDown += ParentWindow_PreviewKeyDown;
}
}
private void ParentWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
if(Command != null && ModifierKey != ModifierKeys.None && Key != Key.None && Keyboard.Modifiers == ModifierKey && e.Key == Key)
Command.Execute(null);
}
private void AssociatedObject_Unloaded(object sender, RoutedEventArgs e)
{
if(_parentWindow != null)
{
_parentWindow.PreviewKeyDown -= ParentWindow_PreviewKeyDown;
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObject_Loaded;
AssociatedObject.Unloaded -= AssociatedObject_Loaded;
}
}
Sample usage:
<TextBox xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<i:Interaction.Behaviors>
<local:KeyboardShortcutBehavior ModifierKey="Ctrl" Key="U" Command="{Binding myCommand}" />
</i:Interaction.Behaviors>
</TextBox>
In code behind easy. Create some utility function that eventually lead to an observable of the parent window key events. Note that you will need the ReactiveUI.Events library.
Some utils for handling load and unload of controls.
public static void LoadUnloadHandler
( this FrameworkElement control
, Func<IDisposable> action
)
{
var state = false;
var cleanup = new SerialDisposable();
Observable.Merge
(Observable.Return(control.IsLoaded)
, control.Events().Loaded.Select(x => true)
, control.Events().Unloaded.Select(x => false)
)
.Subscribe(isLoadEvent =>
{
if (!state)
{
// unloaded state
if (isLoadEvent)
{
state = true;
cleanup.Disposable = new CompositeDisposable(action());
}
}
else
{
// loaded state
if (!isLoadEvent)
{
state = false;
cleanup.Disposable = Disposable.Empty;
}
}
});
}
public static IObservable<T> LoadUnloadHandler<T>(this FrameworkElement control, Func<IObservable<T>> generator)
{
Subject<T> subject = new Subject<T>();
control.LoadUnloadHandler(() => generator().Subscribe(v => subject.OnNext(v)));
return subject;
}
and one specifically for handling the window of a loaded control
public static IObservable<T> LoadUnloadHandler<T>
(this FrameworkElement control, Func<Window, IObservable<T>> generator)
{
Subject<T> subject = new Subject<T>();
control.LoadUnloadHandler(() => generator(Window.GetWindow(control)).Subscribe(v => subject.OnNext(v)));
return subject;
}
and finally a key handler for the parent window of any control
public static IObservable<KeyEventArgs> ParentWindowKeyEventObservable(this FrameworkElement control)
=> control.LoadUnloadHandler((Window window) => window.Events().PreviewKeyDown);
now you can do
Button b;
b.ParentWindowKeyEventObservable()
.Subscribe( kEvent => {
myCommand.Execute();
}
It might seem a bit complex but I use the LoadUnloadHandler on most user controls to aquire and dispose resources as the UI lifecycle progresses.
You want to use KeyBindings for this. This allows you to bind keyboard key combos to a command. Read the docs here: https://msdn.microsoft.com/en-us/library/system.windows.input.keybinding(v=vs.110).aspx
there is a propery on mainwindow of my app that is updated by a function taht runs in background (DoWork). BackgroundWorker is implemented in ViewModel. If I open an new page and comme back on the mainwindow this property takes automatically its default value with which it was initialized in the ViewModel constructor.
What should I do to keep this property updated even if a new window is opened?
public class ImageViewModel : INotifyPropertyChanged
{
private string currentData;
public ImageViewModel()
{
img = new ImageFile { path = "" };
currentData = "There is currently no update";
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
this.worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_Completed);
this.worker.WorkerReportsProgress = true;
}
public string CurrentData
{
get { return this.currentData; }
private set
{
if (this.currentData != value)
{
this.currentData = value;
this.RaisePropertyChanged("CurrentData");
}
}
}
...
private void DoWork(object sender, DoWorkEventArgs e)
{
...
this.CurrentData = "file X is being updated...";
...
}
void worker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
this.CurrentData = "There is currently no update...";
}
You can create a Singleton for your ViewModel like this:
Add this to your ViewModel class:
public static YourViewModelType Instance { get; set; }
In your Window.xaml.cs then assign the DataContext like this:
if(YourViewModel.Instance == null)
{
YourViewModel.Instance = new YourViewModelType();
}
this.DataContext = YourViewModel.Instance;
Note:
This will cause all your Windows to have the same DataContext. You should only do this if every Window needs the same Properties (Bindings) and stuff.
Otherwise I strongly recommend using different ViewModels per window.
I want to acces a owner of flyout which i created.
I have code:
public void dosomething(Grid lessonGrid)
{
var invisibleButton = new Button();
lessonGrid.Children.Add(invisibleButton);
var contentGrid = new Grid()
var buttonInFlyOut = new Button { Content="Click" };
buttonInFlyOut.Click += buttonClicked;
contentGrid.Children.Add(buttonInFlyOut);
var flyout = new FlyoutForLessons {
Content = contentGrid
};
flyout.Closed += (f, h) =>
{
lessonGrid.Children.Remove(invisibleButton);
};
flyout.Owner = lessonGrid;
flyout.ShowAt(invisibleButton); // i want to acces a owner from parent of invisible Button -> lessonGrid
}
private class FlyoutForLessons : Flyout
{
private static readonly DependencyProperty OwnerOfThisFlyOutProperty = DependencyProperty.Register(
"owner", typeof(UIElement), typeof(FlyoutForLessons),
null);
public UIElement Owner
{
get { return (UIElement) GetValue(OwnerOfThisFlyOutProperty); }
set { SetValue(OwnerOfThisFlyOutProperty, value); }
}
}
This code shows me a flyout. So when i click a button "buttonInFlyOut" i want to get id of "lessonGrid" from sender in this method:
private void buttonClicked_Click(object sender, RoutedEventArgs e)
{
}
As you see, i tried to create a new Flyout with custom property, but i can't get this flyout from sender at method above. I don't know how to do it, and i don't want to create a private static variable which keeps an instance of grid where flyout's appears.
If live tree helps:
There's no reason why you couldn't handle your click event like this:
public void DoSomething(Grid lessonGrid)
{
var invisibleButton = new Button();
lessonGrid.Children.Add(invisibleButton);
var contentGrid = new Grid();
var buttonInFlyOut = new Button { Content = "Click" };
buttonInFlyOut.Click += (o, args) =>
{
this.OnButtonClicked(lessonGrid);
};
contentGrid.Children.Add(buttonInFlyOut);
var flyout = new Flyout { Content = contentGrid };
flyout.Closed += (f, h) => { lessonGrid.Children.Remove(invisibleButton); };
flyout.ShowAt(invisibleButton); // i want to acces a owner from parent of invisible Button -> lessonGrid
}
private void OnButtonClicked(Grid lessonGrid)
{
// Do something here
}
This allows you to access the grid that you passed in to the method.
Due to the way that Flyout isn't a FrameworkElement, you'll never find it in the visual tree which is why you see the pop up in your screenshot is outside of the frame. Without either setting a property that you access in your method or trying it in the way I've described above, I don't think it's possible to do this.
I am writing a WPF application that programmatically creates a few buttons. How do you create an OnClick command for a button in the ViewModel? I would like to add a command to clear all TextBoxes with the ResetButton.
new StackPanel
{
Orientation = Orientation.Horizontal,
Children =
{
new Button { Name = "SendButton", Content = "Send", MinWidth = 50, MaxHeight = 30, Margin = new Thickness(5), Background = Brushes.DodgerBlue },
new Button { Name = "ResetButton", Content = "Reset", MinWidth = 50, MaxHeight = 30, Margin = new Thickness(5), Background = Brushes.DarkRed}
}
});
Do you have access to the view model as you are creating the Stack Panel?
If so, you have your View Model expose a Command:
var myViewModel = (MyViewModel)this.DataContext;
Button sendButton = new Button
{
Name = "SendButton",
Command = myViewModel.SendCommand,
// etcd
}
And in your view model:
class MyViewModel : INotifyPropertyChanged
{
private class SendCommand : ICommand
{
private readonly MyViewModel _viewModel;
public SendCommand(MyViewModel viewModel)
{
this._viewModel = viewModel;
}
void ICommand.Execute(object parameter)
{
_viewModel.Send();
}
bool ICommand.CanExecute(object p)
{
// Could ask the view nodel if it is able to execute
// the command at this moment
return true;
}
}
public ICommand SendCommand
{
get
{
return new SendCommand(this);
}
}
internal void Send()
{
// Invoked by your command class
}
}
This example creates a new class just for this one command. After you've done this more than once you'll probably see a pattern, and wrap it up in a generic utility class. See http://www.wpftutorial.net/delegatecommand.html for an example, or use any of the myriad of WPF extension libraries.
Answer to your first question:
How do you create an OnClick command for a button in the ViewModel?
You can actually do this to add onclick for a button:
Button button = new Button { Name = "ResetButton"};
button.Click += button_Click; (button_Click is the name of method)
void button_Click(object sender, RoutedEventArgs e)
{
//do what you want to do when the button is pressed
}
by the way, Andrew's solution is better. ooaps.