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;
}
}
Related
I need to inherit some Editable TextBox properties and add them within the CompoBox properties
For example IsFocused is for reading only
And the SelectionLength is for reading and writing
I made a class inheriting CompoBox
I added a new propertie and tried to change it via the GotFocus
But it doesn't seem to work.
public class MyComboBox : ComboBox
{
TextBox? _textBox;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_textBox = Template.FindName("PART_EditableTextBox", this) as TextBox;
_textBox.GotFocus += _textBox_GotFocus;
_textBox.LostFocus += _textBox_LostFocus;
}
private void _textBox_LostFocus(object sender, RoutedEventArgs e)
{
TextBoxIsFocused = false;
}
private void _textBox_GotFocus(object sender, RoutedEventArgs e)
{
TextBoxIsFocused = true;
}
public static readonly DependencyProperty TextBoxIsFocusedProperty =
DependencyProperty.RegisterAttached(
"TextBoxIsFocused", typeof(bool), typeof(MyComboBox),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.AffectsRender));
public static void SetTextBoxIsFocused(ComboBox cb, bool value)
{
cb.SetValue(TextBoxIsFocusedProperty, value);
}
public bool TextBoxIsFocused
{
set => SetTextBoxIsFocused(this, value);
}
}
I don't know how I can update TextBoxIsFocused automatically when the _textBox!. IsFocused value changed
The same goes for writable properties.
How can I set snd get for EditableTextBox properties
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
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 have created a custom TextEditor control that inherits from AvalonEdit. I have done this to facilitate the use of MVVM and Caliburn Micro using this editor control. The [cut down for display purposes] MvvTextEditor class is
public class MvvmTextEditor : TextEditor, INotifyPropertyChanged
{
public MvvmTextEditor()
{
TextArea.SelectionChanged += TextArea_SelectionChanged;
}
void TextArea_SelectionChanged(object sender, EventArgs e)
{
this.SelectionStart = SelectionStart;
this.SelectionLength = SelectionLength;
}
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
target.SelectionLength = (int)args.NewValue;
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
set { SetValue(SelectionLengthProperty, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string caller = null)
{
var handler = PropertyChanged;
if (handler != null)
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
Now, in the view that holds this control, I have the following XAML:
<Controls:MvvmTextEditor
Caliburn:Message.Attach="[Event TextChanged] = [Action DocumentChanged()]"
TextLocation="{Binding TextLocation, Mode=TwoWay}"
SyntaxHighlighting="{Binding HighlightingDefinition}"
SelectionLength="{Binding SelectionLength,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"
Document="{Binding Document, Mode=TwoWay}"/>
My issue is SelectionLength (and SelectionStart but let us just consider the length for now as the problem is the same). If I selected something with the mouse, the binding from the View to my View Model works great. Now, I have written a find and replace utility and I want to set the SelectionLength (which has get and set available in the TextEditor control) from the code behind. In my View Model I am simply setting SelectionLength = 50, I implement this in the View Model like
private int selectionLength;
public int SelectionLength
{
get { return selectionLength; }
set
{
if (selectionLength == value)
return;
selectionLength = value;
Console.WriteLine(String.Format("Selection Length = {0}", selectionLength));
NotifyOfPropertyChange(() => SelectionLength);
}
}
when I set SelectionLength = 50, the DependencyProperty SelectionLengthProperty does not get updated in the MvvmTextEditor class, it is like the TwoWay binding to my control is failing but using Snoop there is no sign of this. I thought this would just work via the binding, but this does not seem to be the case.
Is there something simple I am missing, or will I have to set up and event handler in the MvvmTextEditor class which listens for changes in my View Model and updated the DP itself [which presents it's own problems]?
Thanks for your time.
This is because the Getter and Setter from a DependencyProperty is only a .NET Wrapper. The Framework will use the GetValue and SetValue itself.
What you can try is to access the PropertyChangedCallback from your DependencyProperty and there set the correct Value.
public int SelectionLength
{
get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectionLength. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor), new PropertyMetadata(0,SelectionLengthPropertyChanged));
private static void SelectionLengthPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var textEditor = obj as MvvmTextEditor;
textEditor.SelectionLength = e.NewValue;
}
Here is another answer if you are still open. Since SelectionLength is already defined as a dependency property on the base class, rather than create a derived class (or add an already existing property to the derived class), I would use an attached property to achieve the same functionality.
The key is to use System.ComponentModel.DependencyPropertyDescriptor to subscribe to the change event of the already existing SelectionLength dependency property and then take your desired action in the event handler.
Sample code below:
public class SomeBehavior
{
public static readonly DependencyProperty IsEnabledProperty
= DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool), typeof(SomeBehavior), new PropertyMetadata(OnIsEnabledChanged));
public static void SetIsEnabled(DependencyObject dpo, bool value)
{
dpo.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(DependencyObject dpo)
{
return (bool)dpo.GetValue(IsEnabledProperty);
}
private static void OnIsEnabledChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var editor = dpo as TextEditor;
if (editor == null)
return;
var dpDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(TextEditor.SelectionLengthProperty,editor.GetType());
dpDescriptor.AddValueChanged(editor, OnSelectionLengthChanged);
}
private static void OnSelectionLengthChanged(object sender, EventArgs e)
{
var editor = (TextEditor)sender;
editor.Select(editor.SelectionStart, editor.SelectionLength);
}
}
Xaml below:
<Controls:TextEditor Behaviors:SomeBehavior.IsEnabled="True">
</Controls:TextEditor>
This is how I did this...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
if (target.SelectionLength != (int)args.NewValue)
{
target.SelectionLength = (int)args.NewValue;
target.Select(target.SelectionStart, (int)args.NewValue);
}
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
//get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
Sorry for any time wasted. I hope this helps someone else...
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.