How could I access a XAML object in my ViewModel? I am really confused. I want to access the <Controls:ModalContentPresenter> object. How could I realise this in a MVVM conform way? On this object I want to call a method ShowModalContent
<Controls:ModalContentPresenter x:Name="modalContent">
<ScrollViewer Behaviors:AdvancedZooming.KeepInCenter="true" Visibility="{Binding LeerformularIsVisible}" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Viewbox Stretch="Uniform">
<Grid>
<DataGrid BorderBrush="{x:Null}">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding AddFieldDefinitionCommand}" Header="Feld hinterlegen" Icon="pack://application:,,,/Images/Designer/field.png" />
<MenuItem Command="{Binding AddFunctionCommand}" Header="Funktion hinterlegen" Icon="pack://application:,,,/Images/Designer/FI_Taschenmesser_16x16.png" />
<MenuItem Command="{Binding RemoveFieldDefinitionCommand}" Header="Aktuelle Felddefinition entfernen" Icon="pack://application:,,,/Images/Designer/remove_field.png" />
<MenuItem Command="{Binding CutCommand}" Header="Ausschneiden" Icon="pack://application:,,,/Images/Zwischenablage/FI_Ausschneiden_16x16.png" />
<MenuItem Command="{Binding CopyCommand}" Header="Kopieren" Icon="pack://application:,,,/Images/Zwischenablage/FI_Kopieren_16x16.png" />
<MenuItem Command="{Binding PasteCommand}" Header="Einfügen" Icon="pack://application:,,,/Images/Zwischenablage/FI_Einfuegen_16x16.png" />
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Grid>
</Viewbox>
</ScrollViewer>
<Controls:ModalContentPresenter.ModalContent>
<StackPanel>
<TextBlock>Test</TextBlock>
<Button>Hide</Button>
</StackPanel>
</Controls:ModalContentPresenter.ModalContent>
</Controls:ModalContentPresenter>
The code of ModalContentPresenter can be found here:
using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
namespace Controls
{
[ContentProperty("Content")]
public class ModalContentPresenter : FrameworkElement
{
#region private fields
private Panel layoutRoot;
private ContentPresenter primaryContentPresenter;
private ContentPresenter modalContentPresenter;
private Border overlay;
private object[] logicalChildren;
private KeyboardNavigationMode cachedKeyboardNavigationMode;
private static readonly TraversalRequest traversalDirection;
#endregion
#region dependency properties
public static readonly DependencyProperty IsModalProperty = DependencyProperty.Register("IsModal", typeof(bool), typeof(ModalContentPresenter),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsModalChanged));
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(ModalContentPresenter),
new UIPropertyMetadata(null, OnContentChanged));
public static readonly DependencyProperty ModalContentProperty = DependencyProperty.Register("ModalContent", typeof(object), typeof(ModalContentPresenter),
new UIPropertyMetadata(null, OnModalContentChanged));
public static readonly DependencyProperty OverlayBrushProperty = DependencyProperty.Register("OverlayBrush", typeof(Brush), typeof(ModalContentPresenter),
new UIPropertyMetadata(new SolidColorBrush(Color.FromArgb(204, 169, 169, 169)), OnOverlayBrushChanged));
public bool IsModal
{
get { return (bool)GetValue(IsModalProperty); }
set { SetValue(IsModalProperty, value); }
}
public object Content
{
get { return (object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public object ModalContent
{
get { return (object)GetValue(ModalContentProperty); }
set { SetValue(ModalContentProperty, value); }
}
public Brush OverlayBrush
{
get { return (Brush)GetValue(OverlayBrushProperty); }
set { SetValue(OverlayBrushProperty, value); }
}
#endregion
#region routed events
public static readonly RoutedEvent PreviewModalContentShownEvent = EventManager.RegisterRoutedEvent("PreviewModalContentShown", RoutingStrategy.Tunnel,
typeof(RoutedEventArgs), typeof(ModalContentPresenter));
public static readonly RoutedEvent ModalContentShownEvent = EventManager.RegisterRoutedEvent("ModalContentShown", RoutingStrategy.Bubble,
typeof(RoutedEventArgs), typeof(ModalContentPresenter));
public static readonly RoutedEvent PreviewModalContentHiddenEvent = EventManager.RegisterRoutedEvent("PreviewModalContentHidden", RoutingStrategy.Tunnel,
typeof(RoutedEventArgs), typeof(ModalContentPresenter));
public static readonly RoutedEvent ModalContentHiddenEvent = EventManager.RegisterRoutedEvent("ModalContentHidden", RoutingStrategy.Bubble,
typeof(RoutedEventArgs), typeof(ModalContentPresenter));
public event RoutedEventHandler PreviewModalContentShown
{
add { AddHandler(PreviewModalContentShownEvent, value); }
remove { RemoveHandler(PreviewModalContentShownEvent, value); }
}
public event RoutedEventHandler ModalContentShown
{
add { AddHandler(ModalContentShownEvent, value); }
remove { RemoveHandler(ModalContentShownEvent, value); }
}
public event RoutedEventHandler PreviewModalContentHidden
{
add { AddHandler(PreviewModalContentHiddenEvent, value); }
remove { RemoveHandler(PreviewModalContentHiddenEvent, value); }
}
public event RoutedEventHandler ModalContentHidden
{
add { AddHandler(ModalContentHiddenEvent, value); }
remove { RemoveHandler(ModalContentHiddenEvent, value); }
}
#endregion
#region ModalContentPresenter implementation
static ModalContentPresenter()
{
traversalDirection = new TraversalRequest(FocusNavigationDirection.First);
}
public ModalContentPresenter()
{
layoutRoot = new ModalContentPresenterPanel();
primaryContentPresenter = new ContentPresenter();
modalContentPresenter = new ContentPresenter();
overlay = new Border();
AddVisualChild(layoutRoot);
logicalChildren = new object[2];
overlay.Background = OverlayBrush;
overlay.Child = modalContentPresenter;
overlay.Visibility = Visibility.Hidden;
layoutRoot.Children.Add(primaryContentPresenter);
layoutRoot.Children.Add(overlay);
}
public void ShowModalContent()
{
if (!IsModal)
IsModal = true;
}
public void HideModalContent()
{
if (IsModal)
IsModal = false;
}
private void RaiseModalContentShownEvents()
{
RoutedEventArgs args = new RoutedEventArgs(PreviewModalContentShownEvent);
OnPreviewModalContentShown(args);
if (!args.Handled)
{
args = new RoutedEventArgs(ModalContentShownEvent);
OnModalContentShown(args);
}
}
private void RaiseModalContentHiddenEvents()
{
RoutedEventArgs args = new RoutedEventArgs(PreviewModalContentHiddenEvent);
OnPreviewModalContentHidden(args);
if (!args.Handled)
{
args = new RoutedEventArgs(ModalContentHiddenEvent);
OnModalContentHidden(args);
}
}
protected virtual void OnPreviewModalContentShown(RoutedEventArgs e)
{
RaiseEvent(e);
}
protected virtual void OnModalContentShown(RoutedEventArgs e)
{
RaiseEvent(e);
}
protected virtual void OnPreviewModalContentHidden(RoutedEventArgs e)
{
RaiseEvent(e);
}
protected virtual void OnModalContentHidden(RoutedEventArgs e)
{
RaiseEvent(e);
}
#endregion
#region property changed callbacks
private static void OnIsModalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ModalContentPresenter control = (ModalContentPresenter)d;
if ((bool)e.NewValue == true)
{
control.cachedKeyboardNavigationMode = KeyboardNavigation.GetTabNavigation(control.primaryContentPresenter);
KeyboardNavigation.SetTabNavigation(control.primaryContentPresenter, KeyboardNavigationMode.None);
control.overlay.Visibility = Visibility.Visible;
control.overlay.MoveFocus(traversalDirection);
control.RaiseModalContentShownEvents();
}
else
{
control.overlay.Visibility = Visibility.Hidden;
KeyboardNavigation.SetTabNavigation(control.primaryContentPresenter, control.cachedKeyboardNavigationMode);
control.primaryContentPresenter.MoveFocus(traversalDirection);
control.RaiseModalContentHiddenEvents();
}
}
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ModalContentPresenter control = (ModalContentPresenter)d;
if (e.OldValue != null)
control.RemoveLogicalChild(e.OldValue);
control.primaryContentPresenter.Content = e.NewValue;
control.AddLogicalChild(e.NewValue);
control.logicalChildren[0] = e.NewValue;
}
private static void OnModalContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ModalContentPresenter control = (ModalContentPresenter)d;
if (e.OldValue != null)
control.RemoveLogicalChild(e.OldValue);
control.modalContentPresenter.Content = e.NewValue;
control.AddLogicalChild(e.NewValue);
control.logicalChildren[1] = e.NewValue;
}
private static void OnOverlayBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ModalContentPresenter control = (ModalContentPresenter)d;
control.overlay.Background = (Brush)e.NewValue;
}
#endregion
#region FrameworkElement overrides
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index > 1)
throw new ArgumentOutOfRangeException("index");
return layoutRoot;
}
protected override int VisualChildrenCount
{
get { return 1; }
}
protected override IEnumerator LogicalChildren
{
get { return logicalChildren.GetEnumerator(); }
}
protected override Size ArrangeOverride(Size finalSize)
{
layoutRoot.Arrange(new Rect(finalSize));
return finalSize;
}
protected override Size MeasureOverride(Size availableSize)
{
layoutRoot.Measure(availableSize);
return layoutRoot.DesiredSize;
}
#endregion
#region layout panel
class ModalContentPresenterPanel : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
Size resultSize = new Size(0, 0);
foreach (UIElement child in Children)
{
child.Measure(availableSize);
resultSize.Width = Math.Max(resultSize.Width, child.DesiredSize.Width);
resultSize.Height = Math.Max(resultSize.Height, child.DesiredSize.Height);
}
return resultSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement child in InternalChildren)
{
child.Arrange(new Rect(finalSize));
}
return finalSize;
}
}
#endregion
}
}
You shouldn't do this (at least, this is definitely not a MVVM-way).
Use IsModal property and data binding instead:
<Controls:ModalContentPresenter x:Name="modalContent" IsModal="{Binding IsModal}">
where IsModal in the binding expression is a bound data property within your view model.
In MVVM, any data going back and forth should be done via data binding properties on your View Model. That includes virtually any properties of the ContentPresenter - just add a binding. However, if you want call a method of a XAML object in an MVVM-friendly way, you can use an Action.
Let's suppose I wanted to set the focus on a textbox via the view model (contrived example, I know there's other MVVM-ways to do this- just wanted an example that requires a child object of the view).
First, give the textbox that should get the focus a name in your XAML, i.e.,
<TextBox x:Name="textBox1"/>
On your view model, add a property for the Action:
public Action FocusAction {get;set;}
Some time before or as your view is loading, get your DataContext (i.e., your view model) and add the Action to the code behind (the view's .cs file):
ViewModel vm = (ViewModel)this.DataContext;
if ( vm.FocusAction == null )
vm.FocusAction= new Action(() => this.textBox1.Focus());
For instance, you might implement the IsLoaded event and do it there. You could also do this in your constructor after InitializeComponent, as long as it either created your view model instance or it was passed in as a paramater. The key is that the view model is already instantiated and assigned to the view's data context.
Then in your view model, wherever you wanted to add focus to that textbox, call:
FocusAction();
Since what I just showed above requires that your view cast the DataContext to a particular view model, what I do is create an interface for the kinds of actions I need, like this:
interface IFocusable
{
Action FocusAction {get;set;}
}
Then I make my view model implement that inteface. With that done, in my view's code-behind, I can say something like:
if(this.DataContext is IFocusable)
((IFocusable)this.DataContext).FocusAction = new Action(() => this.textBox1.Focus());
I think that makes it more MVVM-compliant, since it's not tightly-coupled to a particular view model, the view just knows it can add an action if the view model is the type of view model that can use it.
More details and another example available here: http://jkshay.com/closing-a-wpf-window-using-mvvm-and-minimal-
code-behind/
I know it's been few years but I just faced the same, so here's my answer in case someone will find it useful...
In your xaml file refer to your ViewModel class in the DataContext:
<Window.DataContext>
<local:YourViewModel x:Name="yourViewModel"/>
</Window.DataContext>
In your ViewModel class create a public function with your xaml file as argument and a private member to hold it:
private MainWindow mainWindow;
public void OnViewInitialized(MainWindow mainWindow)
{
this.mainWindow = mainWindow;
}
In your code behind use the function pass the the 'yourViewModel' you defined in the xaml:
public MainWindow()
{
InitializeComponent();
yourViewModel.OnViewInitialized(this);
}
And that's it :)
Now you can access all your xaml elements from your ViewModel by using your mainWindow member, just give them a name.
mainWindow.textBox1
you can create a constructor for view model which accepts the ContentPage as its parameter.
public class ViewModelClass
{
public ViewModelClass(ContentPage p=null)
{...}
}
then set the binding context in Contentpage back code script passing the referenc of contentpage to viewmodel
public class ContentPageClass : ContentPage
{
public ContentPageClass()
{
BindingContext = new ViewModelClass(p:this);
}
}
Related
I am using a set of standard buttons, reused across a WPF app and want to add a shortcut key to each of the buttons.
So I have a ControlTemplate containing the buttons with Command bound to standard commands
I am trying to add a KeyBinding to the UserControl that contains the Button by adding Dependency Property to the Button and navigating up the Parent tree to the nearest UserControl.
I can get the DP values to set Key and Modifiers from the template Button, but cannot get hold of the Command (maybe because it is not bound until later??)
Any ideas how I can:
Either get hold of or create the Command from the Template per this approach
Or get the Command when it is resolved and then set the KeyBinding
PS: I have set the Key and Modifiers in separate DPs but would prefer to have a single DP of KeyBinding, then set ShortcutBinding.Key and ShortcutBinding.Modifers in XAML.
Is there a way to set the properties of a DP class in XAML like that?
Extract of the XAML of from the button group template:
<ctrl:ButtonShortcut
x:Name="btnUpdate"
Style="{StaticResource EditButtonStyle}"
Command="{Binding DataContext.UpdateCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Content="Update"
ShortcutKey="U"
ShortcutModifiers="Ctrl"/>
The DP class, inherited from Button, implementing the Key and Modifiers to link with the same Command that is bound to the Button:
public partial class ButtonShortcut : Button
{
public KeyBinding ShortcutBinding { get; set; }
public Key ShortcutKey
{
get { return (Key)GetValue(ShortcutKeyProperty); }
set { SetValue(ShortcutKeyProperty, value); }
}
public static readonly DependencyProperty ShortcutKeyProperty =
DependencyProperty.Register("ShortcutKey", typeof(Key), typeof(ButtonShortcut), new PropertyMetadata(Key.None, ShortcutKeyChanged));
public ModifierKeys ShortcutModifiers
{
get { return (ModifierKeys)GetValue(ShortcutModifiersProperty); }
set { SetValue(ShortcutModifiersProperty, value); }
}
public static readonly DependencyProperty ShortcutModifiersProperty =
DependencyProperty.Register("ShortcutModifiers", typeof(ModifierKeys), typeof(ButtonShortcut), new PropertyMetadata(ModifierKeys.None, ShortcutKeyChanged));
private static void ShortcutKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var btn = d as ButtonShortcut;
if (btn != null)
{
FrameworkElement uc = btn.Parent as FrameworkElement;
while (uc?.Parent != null && uc is not UserControl)
uc = uc.Parent as FrameworkElement;
if (btn.ShortcutBinding == null)
{
btn.ShortcutBinding = new KeyBinding();
}
var bindings = btn.CommandBindings;
if (e.NewValue is Key)
btn.ShortcutBinding.Key = (Key)e.NewValue;
if (e.NewValue is ModifierKeys)
btn.ShortcutBinding.Modifiers = (ModifierKeys)e.NewValue;
//So far, so good, but I cannot find the Command to apply to the KeyBinding
btn.ShortcutBinding.Command = btn.Command;
btn.ShortcutBinding.CommandParameter = btn.CommandParameter;
if (btn.Command == null)
System.Diagnostics.Debug.Print("not in Commmand");
if (btn.CommandBindings.Count == 0)
System.Diagnostics.Debug.Print("not in CommandBindings");
if (btn.ReadLocalValue(CommandProperty) == DependencyProperty.UnsetValue)
System.Diagnostics.Debug.Print("not in DP CommandProperty");
if (btn.ShortcutBinding.Key != Key.None && uc != null)
{
if (!uc.InputBindings.Contains(btn.ShortcutBinding))
uc.InputBindings.Add(btn.ShortcutBinding);
}
}
}
public ButtonShortcut()
{
InitializeComponent();
}
}
This looks like a very bad smelling design. The child control should not configure the parent control. Especially not to add "features" that the child pretends to offer: in your case the button is not able to execute key gestures - it only defines them.
Since the parent control is the target of the gestures, it must define the required handlers to execute the gesture. This means, all the action and responsibilities are in the parent UserControl and not in the Button (or child element). Classes should never know details of other classes. A button should never know which parent will execute its command and how to register the command in the first place.
The button is just a passive source. It does not handle commands or gestures itself. Therefore, it would never register a CommandBinding or InputBinding.
Usually, you bind the Button.Command to a command defined on the command target. The button invokes this command on the target (or raises a RoutedCommand) and the target executes a corresponding operation.
It doesn't make sense to define commands or key gestures on a button. The button won't execute them. Commands, key and mouse gestures must be defined on the target, where the related responsibilities are.
You can let the parent UserControl (which I assume in this example is the command target) define a RoutedCommand. Register the corresponding key gesture(s) with this command:
Solution 1
MyUserControl.xaml.cs
partial class MyUserControl : UserControl
{
public static RoutedCommand DoActionCommand { get; }
static MyUserControl()
{
var gestures = new InputGestureCollection
{
new KeyGesture(Key.U, ModifierKeys.Control),
};
DoActionCommand = new RoutedUICommand(
"Do something",
nameof(DoActionCommand),
typeof(CommandTargetUserControl),
gestures);
}
public MyUserControl()
{
InitializeComponent();
this.CommandBindings.Add(new CommandBinding(DoActionCommand, ExecuteDoActionCommand));
}
}
CommandTargetUserControl.xaml
<MyUserControl>
<Button Command="{x:Static local:CommandTargetUserControl.DoActionCommand}" />
</MyUserControl>
Solution 2
To allow configuring default key gestures without modifying the source and the target, you can implement an attached behavior. This behavior basically delegates a hooked command, which is invoked by a key gesture, to the actual command sources that are registered with the particular key gesture. Command source and command target are totally decoupled. Input bindings are configured explicitly to respect encapsulation (no silent and unexpected modidifications).
Usage example
<Window local:KeyGestureDelegate.IsKeyGestureDelegationEnabled="True">
<local:KeyGestureDelegate.TargetKeyGestures>
<InputGestureCollection>
<KeyGesture>Ctrl+U</KeyGesture>
<KeyGesture>Ctrl+Shift+M</KeyGesture>
</InputGestureCollection>
</local:KeyGestureDelegate.TargetKeyGestures>
<StackPanel>
<Button Command="{Binding SomeCommand}"
local:KeyGestureDelegate.SourceKeyGesture="Ctrl+U"
local:KeyGestureDelegate.IsKeyGestureCommandExecutionEnabled="True" />
<Button Command="{Binding SomeOtherCommand}"
local:KeyGestureDelegate.SourceKeyGesture="Shift+Ctrl+M"
local:KeyGestureDelegate.IsKeyGestureCommandExecutionEnabled="True" />
</StackPanel>
</Window>
Implementation example
KeyGestureDelegate.cs
An implementation of the RelayCommand used in this example can be found at Microsoft Docs: Relaying Command Logic.
public class KeyGestureDelegate : DependencyObject
{
// Custom KeyGesture comparer for the Dictionary
private class KeyGestureComparer : EqualityComparer<KeyGesture>
{
public override bool Equals(KeyGesture? x, KeyGesture? y)
=> (x?.Key, x?.Modifiers).Equals((y?.Key, y?.Modifiers));
public override int GetHashCode([DisallowNull] KeyGesture obj)
=> HashCode.Combine(obj.Key, obj.Modifiers);
}
private static ICommand KeyGestureDelegateCommand { get; } = new RelayCommand(ExecuteKeyGestureDelegateCommand);
public static bool GetIsKeyGestureDelegationEnabled(DependencyObject attachedElement) => (bool)attachedElement.GetValue(IsKeyGestureDelegationEnabledProperty);
public static void SetIsKeyGestureDelegationEnabled(DependencyObject attachedElement, bool value) => attachedElement.SetValue(IsKeyGestureDelegationEnabledProperty, value);
public static readonly DependencyProperty IsKeyGestureDelegationEnabledProperty = DependencyProperty.RegisterAttached(
"IsKeyGestureDelegationEnabled",
typeof(bool),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(bool), OnIsKeyGestureDelegationEnabled));
public static bool GetIsKeyGestureCommandExecutionEnabled(DependencyObject attachedElement) => (bool)attachedElement.GetValue(IsKeyGestureCommandExecutionEnabledProperty);
public static void SetIsKeyGestureCommandExecutionEnabled(DependencyObject attachedElement, bool value) => attachedElement.SetValue(IsKeyGestureCommandExecutionEnabledProperty, value);
public static readonly DependencyProperty IsKeyGestureCommandExecutionEnabledProperty = DependencyProperty.RegisterAttached(
"IsKeyGestureCommandExecutionEnabled",
typeof(bool),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(bool), OnIsKeyGestureCommandExecutionEnabled));
public static InputGestureCollection GetTargetKeyGestures(DependencyObject obj) => (InputGestureCollection)obj.GetValue(TargetKeyGesturesProperty);
public static void SetTargetKeyGestures(DependencyObject obj, InputGestureCollection value) => obj.SetValue(TargetKeyGesturesProperty, value);
public static readonly DependencyProperty TargetKeyGesturesProperty = DependencyProperty.RegisterAttached(
"TargetKeyGestures",
typeof(InputGestureCollection),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(InputGestureCollection), OnTargetKeyGesturesChanged));
public static KeyGesture GetSourceKeyGesture(DependencyObject attachedElement) => (KeyGesture)attachedElement.GetValue(SourceKeyGestureProperty);
public static void SetSourceKeyGesture(DependencyObject attachedElement, KeyGesture value) => attachedElement.SetValue(SourceKeyGestureProperty, value);
public static readonly DependencyProperty SourceKeyGestureProperty = DependencyProperty.RegisterAttached(
"SourceKeyGesture",
typeof(KeyGesture),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(KeyGesture), OnSourceKeyGestureChanged));
// Remember added InputBindings to enable later removal
private static Dictionary<UIElement, IList<InputBinding>> InputBindingTargetMap { get; } = new Dictionary<UIElement, IList<InputBinding>>();
// Lookup command sources that map to a particular gesture
private static Dictionary<KeyGesture, IList<ICommandSource>> InputBindingSourceMap { get; } = new Dictionary<KeyGesture, IList<ICommandSource>>(new KeyGestureComparer());
private static void OnIsKeyGestureDelegationEnabled(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not UIElement keyGestureHandler)
{
throw new ArgumentException($"Attached element must be of type {typeof(UIElement)}.");
}
InputGestureCollection gestures = GetTargetKeyGestures(keyGestureHandler);
if ((bool)e.NewValue)
{
RegisterKeyBinding(keyGestureHandler, gestures);
}
else
{
UnregisterKeyBinding(keyGestureHandler);
}
}
private static void OnIsKeyGestureCommandExecutionEnabled(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not ICommandSource commandSource)
{
throw new ArgumentException($"Attached element must be of type {typeof(ICommandSource)}.");
}
KeyGesture keyGesture = GetSourceKeyGesture(attachedElement);
if ((bool)e.NewValue)
{
RegisterCommandBinding(commandSource, keyGesture);
}
else
{
UnregisterCommandBinding(commandSource, keyGesture);
}
}
private static void OnTargetKeyGesturesChanged(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not UIElement keyGestureHandler)
{
throw new ArgumentException($"Attached element must be of type {typeof(UIElement)}.");
}
if (e.OldValue is InputBindingCollection)
{
UnregisterKeyBinding(keyGestureHandler);
}
if (!GetIsKeyGestureDelegationEnabled(keyGestureHandler))
{
return;
}
RegisterKeyBinding(keyGestureHandler, e.NewValue as InputGestureCollection);
}
private static void OnSourceKeyGestureChanged(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not ICommandSource commandSource)
{
throw new ArgumentException($"Attached element must be of type {typeof(ICommandSource)}.");
}
UnregisterCommandBinding(commandSource, e.OldValue as KeyGesture);
if (!GetIsKeyGestureCommandExecutionEnabled(attachedElement))
{
return;
}
RegisterCommandBinding(commandSource, e.NewValue as KeyGesture);
}
private static void ExecuteKeyGestureDelegateCommand(object commandParameter)
{
if (InputBindingSourceMap.TryGetValue(commandParameter as KeyGesture, out IList<ICommandSource> commandSources))
{
foreach (ICommandSource commandSource in commandSources)
{
ExecuteCommandSource(commandSource);
}
}
}
private static void ExecuteCommandSource(ICommandSource commandSource)
{
if (commandSource.Command is RoutedCommand routedCommand)
{
IInputElement commandTarget = commandSource.CommandTarget ?? commandSource as IInputElement;
if (routedCommand.CanExecute(commandSource.CommandParameter, commandTarget))
{
routedCommand.Execute(commandSource.CommandParameter, commandTarget);
}
}
else if (commandSource.Command?.CanExecute(parameter: commandSource.CommandParameter) ?? false)
{
commandSource.Command.Execute(commandSource.CommandParameter);
}
}
private static void RegisterKeyBinding(UIElement keyGestureHandler, InputGestureCollection inputGestureCollection)
{
if (inputGestureCollection == null)
{
return;
}
IList<InputBinding>? inputBindings = new List<InputBinding>();
InputBindingTargetMap.Add(keyGestureHandler, inputBindings);
foreach (KeyGesture gesture in inputGestureCollection.OfType<KeyGesture>())
{
var inputBinding = new KeyBinding(KeyGestureDelegateCommand, gesture) { CommandParameter = gesture };
keyGestureHandler.InputBindings.Add(inputBinding);
inputBindings.Add(inputBinding);
}
}
private static void UnregisterKeyBinding(UIElement keyGestureHandler)
{
if (InputBindingTargetMap.TryGetValue(keyGestureHandler, out IList<InputBinding>? inputBindings))
{
foreach (InputBinding inputBinding in inputBindings)
{
keyGestureHandler.InputBindings.Remove(inputBinding);
}
InputBindingTargetMap.Remove(keyGestureHandler);
}
}
private static void RegisterCommandBinding(ICommandSource commandSource, KeyGesture keyGesture)
{
if (keyGesture == null)
{
return;
}
if (!InputBindingSourceMap.TryGetValue(keyGesture, out IList<ICommandSource>? commandSources))
{
commandSources = new List<ICommandSource>();
InputBindingSourceMap.Add(keyGesture, commandSources);
}
commandSources.Add(commandSource);
}
private static void UnregisterCommandBinding(ICommandSource commandSource, KeyGesture keyGesture)
{
if (keyGesture == null)
{
return;
}
if (InputBindingSourceMap.TryGetValue(keyGesture, out IList<ICommandSource>? commandSources))
{
commandSources.Remove(commandSource);
if (!commandSources.Any())
{
InputBindingSourceMap.Remove(keyGesture);
}
}
}
}
I am creating a behavior for Keypress as I have a specific requirement for triggering a command when a key is pressed in an application. I am not using the WPF KeyBinding as I have several user controls with all having their respective ViewModels. I wrote the below behavior which works fine :-
public class WindowKeyPressBehavior : Behavior<Control>
{
#region Public Properties.
public KeyCommandCollection KeyCommandCollection
{
get => (KeyCommandCollection)GetValue(KeyCommandCollectionProperty);
set => SetValue(KeyCommandCollectionProperty, value);
}
public static readonly DependencyProperty KeyCommandCollectionProperty =
DependencyProperty.Register("KeyCommandCollection",
typeof(KeyCommandCollection),
typeof(WindowKeyPressBehavior),
new PropertyMetadata(null));
#endregion
#region Constructors.
public WindowKeyPressBehavior()
{
KeyCommandCollection = new KeyCommandCollection();
}
#endregion
#region Protected Method Declarations.
protected override void OnAttached()
{
base.OnAttached();
WeakEventManager<Control, RoutedEventArgs>.RemoveHandler(AssociatedObject, nameof(AssociatedObject.Loaded), AssociatedObject_Loaded);
WeakEventManager<Control, RoutedEventArgs>.AddHandler(AssociatedObject, nameof(AssociatedObject.Loaded), AssociatedObject_Loaded);
WeakEventManager<Control, RoutedEventArgs>.RemoveHandler(AssociatedObject, nameof(AssociatedObject.Unloaded), AssociatedObject_Unloaded);
WeakEventManager<Control, RoutedEventArgs>.AddHandler(AssociatedObject, nameof(AssociatedObject.Unloaded), AssociatedObject_Unloaded);
}
#endregion
#region Private Method Declarations.
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
WeakEventManager<Window, KeyEventArgs>.RemoveHandler(Application.Current.MainWindow, nameof(Application.Current.MainWindow.PreviewKeyDown), MainWindow_PreviewKeyDown);
WeakEventManager<Window, KeyEventArgs>.AddHandler(Application.Current.MainWindow, nameof(Application.Current.MainWindow.PreviewKeyDown), MainWindow_PreviewKeyDown);
LoadDataContexts();
}
private void AssociatedObject_Unloaded(object sender, RoutedEventArgs e)
{
UnloadDataContexts();
WeakEventManager<Window, KeyEventArgs>.RemoveHandler(Application.Current.MainWindow, nameof(Application.Current.MainWindow.PreviewKeyDown), MainWindow_PreviewKeyDown);
}
private void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (KeyCommandCollection == null || !KeyCommandCollection.Any(x => x.Key == e.Key))
{
return;
}
try
{
IEnumerable<KeyElement> keyCommands = KeyCommandCollection.Where(x => x.Key == e.Key);
foreach (KeyElement keyCommand in keyCommands)
{
// Run it in an asynchronous way as CanExecute/Execute can be time consuming in certain cases
// preventing the next command to run.
Application.Current?.Dispatcher?.Invoke(() =>
{
KeyElement command = keyCommand;
// If parameterized command is available execute it.
if (command.ParameterizedCommand?.CanExecute(command.CommandParameter) == true)
{
command.ParameterizedCommand.Execute(command.CommandParameter);
}
// If non-parameterized command is available, execute it.
else if (command.Command?.CanExecute() == true)
{
command.Command.Execute();
}
});
}
}
catch (System.Exception)
{
//TODO : Log Any Errors.
}
}
private void LoadDataContexts()
{
foreach (KeyElement keyElement in KeyCommandCollection.Where(x => x?.DataContext == null))
{
keyElement.DataContext = AssociatedObject?.DataContext;
}
}
private void UnloadDataContexts()
{
foreach (KeyElement keyElement in KeyCommandCollection.Where(x => x?.DataContext != null))
{
keyElement.DataContext = null;
}
}
#endregion
}
The KeyElement class inherits from FrameworkElement as below
public class KeyElement : FrameworkElement
{
public Key Key
{
get => (Key)GetValue(KeyProperty);
set => SetValue(KeyProperty, value);
}
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register(
"Key",
typeof(Key),
typeof(KeyElement),
new PropertyMetadata(System.Windows.Input.Key.None));
public DelegateCommand Command
{
get => (DelegateCommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(DelegateCommand),
typeof(KeyElement),
new PropertyMetadata(null));
public DelegateCommand<object> ParameterizedCommand
{
get => (DelegateCommand<object>)GetValue(ParameterizedCommandProperty);
set => SetValue(ParameterizedCommandProperty, value);
}
public static readonly DependencyProperty ParameterizedCommandProperty =
DependencyProperty.Register(
"ParameterizedCommand",
typeof(DelegateCommand<object>),
typeof(KeyElement),
new PropertyMetadata(null));
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(KeyElement),
new PropertyMetadata(null));
public override string ToString()
{
return $"Key : {Key}";
}
}
/// <summary>
/// Collection of Key-Commands.
/// </summary>
public class KeyCommandCollection : ObservableCollection<KeyElement>
{
}
My XAML file is as below :-
<UserControl
x:Class="Test.Views.LanguageSelectionPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:prism="http://prismlibrary.com/"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:behaviors="clr-namespace:Test.Behaviors;assembly=Test.UI.Core"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="True"
d:DesignHeight="1024"
d:DesignWidth="1280">
<i:Interaction.Behaviors>
<behaviors:WindowKeyPressBehavior>
<behaviors:WindowKeyPressBehavior.KeyCommandCollection>
<behaviors:KeyElement
Key="{Binding KioskWorkflow.PrmConfiguration.UpKey}"
ParameterizedCommand="{Binding SwitchCommand}"
CommandParameter="{Binding KioskWorkflow.PrmConfiguration.UpKey}" />
<behaviors:KeyElement
Key="{Binding KioskWorkflow.PrmConfiguration.DownKey}"
ParameterizedCommand="{Binding SwitchCommand}"
CommandParameter="{Binding KioskWorkflow.PrmConfiguration.DownKey}" />
<behaviors:KeyElement
Key="{Binding KioskWorkflow.PrmConfiguration.EnterKey}"
Command="{Binding SubmitCommand}" />
</behaviors:WindowKeyPressBehavior.KeyCommandCollection>
</behaviors:WindowKeyPressBehavior>
</i:Interaction.Behaviors>
<Grid></Grid>
</UserControl>
The Problem I have
I am able to run the application properly and everything seems to be working. The only reason I am unhappy is that I don't want the KeyElement to be a FrameworkElement. Instead I want it to be a plain DependencyObject. Problem I am facing with this being a Dependency Object is that I don't get the DataContext property because of which I am not able to set the Binding properly in Code.
Also I don't want to call the LoadDataContexts method in the behavior.
I tried using the BindingProxy approach but I don't like that approach as well as it needs me to declare a static resource in every page.
I have tried making the class KeyElement inherit from Freezable but that is not helping me either, as Binding is not happening, and even though it is freezable, it is not inheriting the DataContext from the parent i.e. the Behavior/UserControl. Tried several options by using RelativeSource but the DataContext is always null.
Also I get the below error in Output if I replace the FrameworkElement with Freezable:-
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element
I had a look at the KeyBinding class and it does not inherit from FrameworkElement still does not throw any error in output window.
Any way of achieving the behavior without using a FrameworkElement ?
Any suggestions on improving the above code is also welcome.
I have observable collection called (Users) in view model that binded with ListViewControl (lstUsers) in view and what I need is to scroll to current logged in user in List View .
I see in most of examples that used scroll from code behind as following e.g. :
lstUsers.ScrollIntoView(lstUsers[5]);
but what I need is to handle it from view model .
Please advice !
One way of doing this would be to use something like an ICollectionView which has a current item. You can then set IsSynchronizedWithCurrentItem to true to link the current item in the view model to the selected item in the ListView.
Finally handle the event SelectionChanged in the code behind the view to change the scroll position so that it always displays the selected item.
For me the benefit of this method is that the viewmodel is kept unaware of anything about the view which is one of the aims of MVVM. The code behind the view is the perfect place for any code concerning the view only.
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView x:Name="View"
SelectionChanged="Selector_OnSelectionChanged" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Items}"/>
<Button Grid.Row="1" Command="{Binding ChangeSelectionCommand}">Set</Button>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
View.ScrollIntoView(View.SelectedItem);
}
}
public class ViewModel
{
private readonly CollectionViewSource _source = new CollectionViewSource();
public ICollectionView Items
{
get { return _source.View; }
}
public ICommand ChangeSelectionCommand { get; set; }
public ViewModel()
{
SetUp();
ChangeSelectionCommand = new Command(ChangeSelection);
}
private void SetUp()
{
var list = new List<string>();
for (int i = 0; i < 100; i++)
{
list.Add(i.ToString(CultureInfo.InvariantCulture));
}
_source.Source = list;
}
private void ChangeSelection()
{
var random = new Random(DateTime.Now.Millisecond);
var n = random.Next(100);
Items.MoveCurrentToPosition(n);
}
}
public class Command : ICommand
{
private readonly Action _action;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action();
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
_action = action;
}
}
let me share my solution with you
Create your own ListView descendant with dependency property TargetListItem
public class ScrollableListView : ListView
{
/// <summary>
/// Set this property to make ListView scroll to it
/// </summary>
public object TargetListItem
{
get { return (object)GetValue(TargetListItemProperty); }
set { SetValue(TargetListItemProperty, value); }
}
public static readonly DependencyProperty TargetListItemProperty = DependencyProperty.Register(
nameof(TargetListItem), typeof(object), typeof(ScrollableListView), new PropertyMetadata(null, TargetListItemPropertyChangedCallback));
static void TargetListItemPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var owner = (ScrollableListView)d;
owner.ScrollToItem(e.NewValue);
}
public void ScrollToItem(object value)
{
if (value != null && Items != null && Items.Contains(value))
{
ScrollIntoView(value);
}
}
}
create property in ViewModel
object currentListItem;
public object СurrentListItem
{
get => сurrentListItem;
set
{
if (сurrentListItem != value)
{
сurrentListItem = value;
OnPropertyChanged(nameof(СurrentListItem));
}
}
}
bind it
<controls:ScrollableListView ... TargetListItem="{Binding CurrentListItem}"/>
Now you can set CurrentListItem in ViewModel when needed. And the corresponding visual element will become visible in the ListView immediately.
Also maybe you just can use attached property on ListView instead of creating ScrollableListView. But i'm not sure.
Yep, there's always times in MVVM when you need to get at the control. There's various ways of doing this, but here's an easy-ish way of doing it without deriving from the control or messing with routed commands or other such toys what you have in WPF.
In summary:
Create an attached property on your view model.
Set the attached property in XAML to pass the list box back to the view model.
Call .ScrollIntoView on demand.
Note, this is a rough and ready example, make sure your DataContext is set before showing the window.
Code/View Model:
public class ViewModel
{
private ListBox _listBox;
private void ReceiveListBox(ListBox listBox)
{
_listBox = listBox;
}
public static readonly DependencyProperty ListBoxHookProperty = DependencyProperty.RegisterAttached(
"ListBoxHook", typeof (ListBox), typeof (ViewModel), new PropertyMetadata(default(ListBox), ListBoxHookPropertyChangedCallback));
private static void ListBoxHookPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var listBox = (ListBox) dependencyObject;
var viewModel = (ViewModel) listBox.DataContext;
viewModel.ReceiveListBox(listBox);
}
public static void SetListBoxHook(DependencyObject element, ListBox value)
{
element.SetValue(ListBoxHookProperty, value);
}
public static ListBox GetListBoxHook(DependencyObject element)
{
return (ListBox) element.GetValue(ListBoxHookProperty);
}
}
OK, so that will let us get the ListBox passed back to the view; you can do with it as you wish.
Now, just set the property in XAML:
<ListBox wpfApplication1:ViewModel.ListBoxHook="{Binding RelativeSource={RelativeSource Self}}" />
Good to go!
I'm trying to create an adorner layer in which I can place, e.g., notifications. I want to make it general and XAML-authored as much as possible, so I made new a ContentControl, called AdornerContentControl, used like this:
<AdornerDecorator Grid.Row="1" Name="RichTextBoxAdornerDecorator">
<loc:AdornerContentControl x:Name="TestAdorner">
<loc:AdornerContentControl.Decorator>
<Button Width="100" Height="100">ZZZ</Button>
</loc:AdornerContentControl.Decorator>
<RichTextBox>
<!-- some more stuff... -->
</RichTextBox>
</loc:AdornerContentControl>
</AdornerDecorator>
This is the code:
public class AdornerContentControl : ContentControl {
private SimpleAdorner _adorner;
public static readonly DependencyProperty DecoratorProperty = DependencyProperty.Register(
"Decorator", typeof (Control), typeof (AdornerContentControl), new PropertyMetadata(default(Control),
OnDecoratorPropertyChanged));
private static void OnDecoratorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { /*...*/ }
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
try { _adorner = new SimpleAdorner((UIElement)Content, Decorator); }
catch (NullReferenceException) { }
}
public Control Decorator
{
get { return (Control) GetValue(DecoratorProperty); }
set { SetValue(DecoratorProperty, value); }
}
}
public class SimpleAdorner : Adorner{
public SimpleAdorner([NotNull] UIElement adornedElement, Control content) : base(adornedElement)
{
_visuals = new VisualCollection(this);
_adornerLayer = AdornerLayer.GetAdornerLayer(adornedElement);
if (content == null) return;
_visuals.Add(_contentPresenter = new ContentPresenter()
{
Content = content
});
_adornerLayer.Add(this);
}
}
But AdornerLayer.GetAdornerLayer(adornedElement) always returns null (the SimpleAdorner is constructed in OnInitialized).
Should I explicitly create AdornerDecorator ?
Am I supposed to call Content.UpdateLayout somewhere ?
I have a custom control that has a DependencyProperty of type ObservableCollection that is bound to an observableCollection:
<MyControl MyCollectionProperty = {Binding MyObservableCollection} ...
Problem is adding to MyObservableCollection does not update MyCollectionProperty.
I need to completly replace the MyObservableCollection to make it work e.g.
MyObservableCollection = null;
MyObservableCollection = new ObservableCollection(){...}
Is there a better way to deal with this?
EDIT:
public ObservableCollection<string> Columns
{
get { return (ObservableCollection<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
In addition to what grantz has answered, I would suggest to declare the property with type IEnumerable<string> and check at runtime if the collection object implements the INotifyCollectionChanged interface. This provides greater flexibility as to which concrete collection implementation may be used as property value. A user may then decide to have their own specialized implementation of an observable collection.
Note also that in the ColumnsPropertyChanged callback the CollectionChanged event handler is attached to the new collection, but also removed from the old one.
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
"Columns", typeof(IEnumerable<string>), typeof(MyControl),
new PropertyMetadata(null, ColumnsPropertyChanged));
public IEnumerable<string> Columns
{
get { return (IEnumerable<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
private static void ColumnsPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control= (MyControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control.ColumnsCollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += control.ColumnsCollectionChanged;
}
control.UpdateColumns();
}
private void ColumnsCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
// optionally take e.Action into account
UpdateColumns();
}
private void UpdateColumns()
{
...
}
Below is a working example that may help.
In this example, the method OnChanged is called immediately, when the Add button is clicked "Changed" is written to the console.
The Control
public class MyControl : Control
{
public ObservableCollection<string> ExtraColumns
{
get { return (ObservableCollection<string>)GetValue(ExtraColumnsProperty); }
set { SetValue(ExtraColumnsProperty, value); }
}
public static readonly DependencyProperty ExtraColumnsProperty =
DependencyProperty.Register("ExtraColumns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
static void OnChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as MyControl).OnChanged();
}
void OnChanged()
{
if ( ExtraColumns != null )
ExtraColumns.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ExtraColumns_CollectionChanged);
}
void ExtraColumns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("Changed");
}
}
The Window
<Window x:Class="WpfApplication18.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication18"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:MyControl ExtraColumns="{Binding Extras}"/>
<Button Click="Button_Click">Add</Button>
</StackPanel>
</Window>
Window Code Behind
public partial class MainWindow : Window
{
private ObservableCollection<string> _extras = new ObservableCollection<string>( );
public ObservableCollection<string> Extras
{
get { return _extras; }
set
{
if (value != _extras)
{
_extras = value;
}
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Extras.Add("Additional");
}
}