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 ?
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'm trying to set width of UserControl at initialization time in ViewModel, but it receives only zero. When I resize the window, it receives correct width.
MainWindow.xaml extract:
<local:MapControl x:Name="MapControl"
DataContext="{Binding MapViewModel}"
ActualControlWidth="{Binding ActualControlWidth, Mode=OneWayToSource}" />
MapControl.xaml.cs:
public partial class MapControl : UserControl
{
public MapControl()
{
InitializeComponent();
SizeChanged += OnControlSizeChanged;
}
public static readonly DependencyProperty ActualControlWidthProperty = DependencyProperty.Register(
"ActualControlWidth",
typeof(double),
typeof(MapControl),
new FrameworkPropertyMetadata(PropertyChangedCallback)); //here it does not have any influence if i put default(double) or not
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var newVar = e.NewValue;
}
public double ActualControlWidth
{
get { return (double)GetValue(ActualControlWidthProperty); }
set { SetValue(ActualControlWidthProperty, value); } //here comes the value as 785
}
private void OnControlSizeChanged(object sender, SizeChangedEventArgs e)
{
ActualControlWidth = ActualWidth;
}
}
MapViewModel.cs:
public class MapViewModel : ViewModelBase
{
private double _actualControlWidth;
private Map _map;
public MapViewModel()
{
...
}
public Map Map
{
get => _map;
set => Set(() => Map, ref _map, value);
}
public double ActualControlWidth
{
get => _actualControlWidth;
set => _actualControlWidth = value; //this is where the value comes as 0
}
}
Thanks for your help!
The full reproduction example is at my Github https://github.com/czechdude/dependencypropertyissue
You need to update this line:
new FrameworkPropertyMetadata(PropertyChangedCallback)); //here it does not have any influence if i put default(double) or not
To
new FrameworkPropertyMetadata(defaultValue: -1.0D,
new PropertyChangedCallback(MethodToCall));
...
private static void MethodToCall(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// call second method to make instance of MapControl and call a custom method that sets your ActualControlWidth
}
I have the following code:
public partial class PhrasesFrameRendererClass : Frame
{
.....
void getRandomWords() {
// more code here that involves getting random numbers
// and updating a grid's bindingcontext
}
}
In my custom renderer I want to be able to call the getRandomWords on swipe left gesture like below:
public class PhraseFrameCustomRenderer : FrameRenderer
{
UISwipeGestureRecognizer leftSwipeGestureRecognizer;
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
leftSwipeGestureRecognizer = new UISwipeGestureRecognizer();
leftSwipeGestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
leftSwipeGestureRecognizer.NumberOfTouchesRequired = 1;
leftSwipeGestureRecognizer.AddTarget((obj) =>
{
// Call getRandomWords() here
});
}
}
Is this possible? Any ideas on how this could be done?
base.OnElementChanged(e);
leftSwipeGestureRecognizer = new UISwipeGestureRecognizer();
leftSwipeGestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
leftSwipeGestureRecognizer.NumberOfTouchesRequired = 1;
leftSwipeGestureRecognizer.AddTarget((obj) =>
{
// Call getRandomWords() here
var frame = Element as PhrasesFrameRendererClass ;
if(frame!=null){
frame.getRandomWords();
}
});
You can create a BindableProperty of type Command in your custom frame class, call that Command from your renderer and bind your ViewModel's getRandomWords method as a Command
//Your custom control in your PCL project
public partial class PhrasesFrameRendererClass : Frame
{
public static readonly BindableProperty SwipeLeftCommandProperty =
BindableProperty.Create(nameof(SwipeLeftCommand), typeof(ICommand), typeof(PhrasesFrameRendererClass ), null);
public ICommand SwipeLeftCommand
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
}
//Your custom control renderer
public class PhraseFrameCustomRenderer : FrameRenderer
{
UISwipeGestureRecognizer leftSwipeGestureRecognizer;
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
leftSwipeGestureRecognizer = new UISwipeGestureRecognizer();
leftSwipeGestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
leftSwipeGestureRecognizer.NumberOfTouchesRequired = 1;
leftSwipeGestureRecognizer.AddTarget((obj) =>
{
var myFrame = Element as PhrasesFrameRendererClassl
if(myFrame != null){
if(myFrame.SwipeLeftCommand != null && myFrame.SwipeLeftCommand.CanExecute()){
myFrame.SwipeLeftCommand.Execute();
}
}
});
}
}
//Your ViewModel
public class PhrasesViewModel{
public Command GetRandomWordsCommand {get;set;}
public PhrasesViewModel(){
GetRandomWordsCommand = new Command(ExecuteGetRandomWords);
}
private void ExecuteGetRandomWords(){
//Your method goes here
}
}
//Your XAML
<yourControls:PhrasesFrameRendererClass SwipeLeftCommand="{Binding GetRandomWordsCommand }"/>
It may seem more complicated this way, but using commands allows you to separate your application code (Such as getting random phrases) from your rendering code
I have following ribbonButton in my wpf application
<RibbonButton Style="{StaticResource MenuButton}"
Label="{x:Static res:Resources.SaveAs}"
LargeImageSource="..\Images\saveas.png" />
It looks:saveAs with line break
but I need something like:saveAs without line break
I used non breaking space in xaml :
<RibbonButton Style="{StaticResource MenuButton}"
Label="Zapisz jako"
LargeImageSource="..\Images\saveas.png" />
But I also need that string from resx file because of localization. But doesn't work when it is in resx. Is there some solution to put non breaking space into resx? Or should I modify ribbonButton template?
I did a behavior that does the job for me, perhaps it could works for you?
(it seems that you get the exact same problem as I did...)
Usage:
<RibbonButton Command="macro:MacroCommands.CommandMacroSaveAs" Label="Save as"
SmallImageSource="pack://application:,,,/WpfUtil;component/Images/document-save-as16.png"
LargeImageSource="pack://application:,,,/WpfUtil;component/Images/document-save-as32.png"
RibbonTwoLineText.HasTwoLines="False"
>
<i:Interaction.Behaviors>
<behaviors:BehaviorRibbonButton TextWrapping="NoWrap" />
</i:Interaction.Behaviors>
</RibbonButton>
Behavior:
using System.Windows;
using System.Windows.Controls.Ribbon;
using System.Windows.Data;
using System.Windows.Interactivity;
namespace HQ.Wpf.Util.Behaviors
{
public class BehaviorRibbonButton : Behavior<RibbonButton>
{
// ************************************************************************
public TextWrapping TextWrapping
{
get { return (TextWrapping)GetValue(TextWrappingProperty); }
set { SetValue(TextWrappingProperty, value); }
}
// ************************************************************************
public static readonly DependencyProperty TextWrappingProperty =
DependencyProperty.Register("TextWrapping", typeof(TextWrapping), typeof(BehaviorRibbonButton), new UIPropertyMetadata(TextWrapping.Wrap, TextWrappingPropertyChangedCallback));
// ************************************************************************
private static void TextWrappingPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var ribbonButton = dependencyObject as BehaviorRibbonButton;
if (ribbonButton != null)
{
ribbonButton.SetTextWrapping();
}
}
// ************************************************************************
public bool HasTwoLine
{
get
{
if (TextWrapping == TextWrapping.NoWrap)
{
return false;
}
return true;
}
}
// ************************************************************************
private void SetTextWrapping()
{
var ribbonButton = this.AssociatedObject as RibbonButton;
if (ribbonButton != null)
{
var ribbonTwoLineText = UiUtility.FindVisualChild<RibbonTwoLineText>(ribbonButton);
if (ribbonTwoLineText != null)
{
ribbonTwoLineText.LineStackingStrategy = LineStackingStrategy.BlockLineHeight;
var binding = new Binding("HasTwoLine");
binding.Source = this;
binding.Mode = BindingMode.OneWay;
ribbonTwoLineText.SetBinding(RibbonTwoLineText.HasTwoLinesProperty, binding);
}
}
}
// ************************************************************************
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += AssociatedObjectOnLoaded;
}
// ************************************************************************
private void AssociatedObjectOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
SetTextWrapping();
}
// ************************************************************************
}
}
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);
}
}