I try to build a ContentDialog similar to the example in https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.contentdialog with mvvm.
For the CanExecute validation I have created a derived ContentDialog class described in https://social.msdn.microsoft.com/Forums/en-US/6d5d6fd9-5f03-4cb6-b6c0-19ca01ddaab8/uwpcontentdialog-buttons-do-not-respect-canexecute?forum=wpdevelop
This works but how can I enable the button so it is clickable for the CanExecute validation.
There is a missing link in the CanExecuteChanged event of the ICommand interface when binding to Views. It works only when it's a perfect situation and with my experience it's mostly never perfect for it.
The trick is to call CanExecuteChanged anytime the proper value changes that should switch the CanExecute to true or false.
If you're using a relay command, what I've done is add a public method to the relay command.
public UpdateCanExecute() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
Then in the property or properties or values that change whether or not this should return true or false just call that method.
public bool IsWorking
{
get { return isWorking; }
set
{
isWorking = value;
Notify(nameof(IsWorking));
MyRelayCommand.UpdateCanExecute();
}
}
This might give you an idea of what I'm talking about. If not or if you need more help I can post more code to this answer to help clarify.
This is my solution. I' ve created an contentdialog extension. The extension contains
a CancelableCommand command
a CancelableCommandParameter parameter
and the bindable DialogCancel property.
These are the attached properties of my extension.
namespace BSE.UI.Xaml.Controls.Extensions
{
public static class ContentDialog
{
public static readonly DependencyProperty DialogCancelProperty =
DependencyProperty.RegisterAttached("DialogCancel",
typeof(bool),
typeof(ContentDialog), new PropertyMetadata(false));
public static readonly DependencyProperty CancelableCommandParameterProperty =
DependencyProperty.Register("CancelableCommandParameter",
typeof(object),
typeof(ContentDialog), null);
public static readonly DependencyProperty CancelableCommandProperty =
DependencyProperty.RegisterAttached("CancelableCommand",
typeof(ICommand),
typeof(ContentDialog),
new PropertyMetadata(null, OnCancelableCommandChanged));
public static void SetDialogCancel(DependencyObject obj, bool value)
{
obj.SetValue(DialogCancelProperty, value);
}
public static bool GetDialogCancel(DependencyObject obj)
{
return (bool)obj.GetValue(DialogCancelProperty);
}
public static ICommand GetCancelableCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CancelableCommandProperty);
}
public static void SetCancelableCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CancelableCommandProperty, value);
}
public static object GetCancelableCommandParameter(DependencyObject obj)
{
return obj.GetValue(CancelableCommandParameterProperty);
}
public static void SetCancelableCommandParameter(DependencyObject obj, object value)
{
obj.SetValue(CancelableCommandParameterProperty, value);
}
private static void OnCancelableCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var contentDialog = obj as Windows.UI.Xaml.Controls.ContentDialog;
if (contentDialog != null)
{
contentDialog.Loaded += (sender, routedEventArgs) =>
{
((Windows.UI.Xaml.Controls.ContentDialog)sender).PrimaryButtonClick += OnPrimaryButtonClick;
};
}
}
private static void OnPrimaryButtonClick(Windows.UI.Xaml.Controls.ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
var contentDialog = sender as Windows.UI.Xaml.Controls.ContentDialog;
if (contentDialog != null)
{
var command = GetCancelableCommand(contentDialog);
command?.Execute(GetCancelableCommandParameter(contentDialog));
args.Cancel = GetDialogCancel(contentDialog);
}
}
}
}
The xaml of the contentdialog looks like that
<ContentDialog
x:Class="MyClass.Views.MyContentDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyClass.Views"
xmlns:controlextensions="using:BSE.UI.Xaml.Controls.Extensions"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
PrimaryButtonText="Button1"
SecondaryButtonText="Button2"
controlextensions:ContentDialog.DialogCancel="{Binding Cancel}"
controlextensions:ContentDialog.CancelableCommandParameter="{Binding}"
controlextensions:ContentDialog.CancelableCommand="{Binding MyCancelableCommand}">
</ContentDialog>
This is the viewmodel
namespace MyClass.ViewModels
{
public class MyContentDialogViewModel : ViewModelBase
{
private ICommand m_myCancelableCommand;
private bool m_cancel;
public ICommand MyCancelableCommand=> m_myCancelableCommand ?? (m_myCancelableCommand = new RelayCommand<object>(CancelableSave));
public bool Cancel
{
get
{
return m_cancel;
}
set
{
m_cancel = value;
RaisePropertyChanged("Cancel");
}
}
private void CancelableSave(object obj)
{
Cancel = !ValidateDialog();
}
private bool ValidateDialog()
{
return true// if saving successfull otherwise false
}
}
}
Related
I'm trying to create a UserControl that exposes 2 other controls, UI and Host, so that I can use MediaPlayerWpf and set properties in the designer like UI.AllowPause and Host.Volume
I understand Attached Properties are what I need. Furthermore, I need to allow setting the Host, while UI only exposes its properties but can't be changed.
I can't get properties UI and Host to display in the designer. What am I doing wrong?
public partial class MediaPlayerWpf {
public MediaPlayerWpf() {
InitializeComponent();
SetUI(this, MediaUI);
}
public static DependencyPropertyKey UIPropertyKey = DependencyProperty.RegisterAttachedReadOnly("UI", typeof(PlayerControls), typeof(MediaPlayerWpf), new PropertyMetadata(null));
public static DependencyProperty UIProperty = UIPropertyKey.DependencyProperty;
public PlayerControls UI { get => (PlayerControls)base.GetValue(UIProperty); private set => base.SetValue(UIPropertyKey, value); }
[AttachedPropertyBrowsableForType(typeof(MediaPlayerWpf))]
public static PlayerControls GetUI(UIElement element) {
return (PlayerControls)element.GetValue(UIProperty);
}
public static void SetUI(UIElement element, PlayerControls value) {
element.SetValue(UIPropertyKey, value);
}
public static DependencyProperty HostProperty = DependencyProperty.RegisterAttached("Host", typeof(PlayerBase), typeof(MediaPlayerWpf), new PropertyMetadata(null, OnHostChanged));
public PlayerBase Host { get => (PlayerBase)base.GetValue(HostProperty); set => base.SetValue(HostProperty, value); }
private static void OnHostChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
MediaPlayerWpf P = d as MediaPlayerWpf;
if (e.OldValue != null)
P.HostGrid.Children.Remove(e.OldValue as PlayerBase);
if (e.NewValue != null) {
P.HostGrid.Children.Add(e.NewValue as PlayerBase);
GetUI(P).PlayerHost = e.NewValue as PlayerBase;
}
}
[AttachedPropertyBrowsableForType(typeof(MediaPlayerWpf))]
public static PlayerBase GetHost(UIElement element) {
return (PlayerBase)element.GetValue(HostProperty);
}
public static void SetHost(UIElement element, PlayerBase value) {
element.SetValue(HostProperty, value);
}
}
I've recently discovered that you should bind passwords in WPF. I found my answer to my problem for my login window, but I'm having a hard time using my helper class to solve the binding problem: PasswordHelper. This class is in one of my project's folder called Utils.
Here's part of my XAML code:
<Window x:Class="IPdevices.LoginWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:IPdevices"
xmlns:utils="clr-namespace:IPdevices.Utils;assembly=Utils"
mc:Ignorable="d"
Title="IPDevices" Height="451" Width="397.5" Icon="logo3_q2J_icon.ico">
notice the xmlns:utils="clr-namespace:IPdevices.Utils;assembly=Utils"
My password box is now
<PasswordBox utils:PasswordHelper.BindPassword="true" utils:PasswordHelper.BoundPassword="{Binding Path=NetworkPassword, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
However, this doesn't compile because it's screaming:
"The attachable property 'BindPassword' was not found in type 'PasswordHelper'."
It's also screaming:
"The name 'PasswordHelper' does not exist in the namespace 'clr-namespace;IPdevices.Uitls;assembly=Utils'"
And here's this PasswordHelper class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace IPdevices.Utils
{
public static class PasswordHelper
{
public static readonly DependencyProperty BoundPassword =
DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordHelper), new PropertyMetadata(string.Empty, OnBoundPasswordChanged));
public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached(
"BindPassword", typeof(bool), typeof(PasswordHelper), new PropertyMetadata(false, OnBindPasswordChanged));
private static readonly DependencyProperty UpdatingPassword =
DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordHelper), new PropertyMetadata(false));
private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox box = d as PasswordBox;
// only handle this event when the property is attached to a PasswordBox
// and when the BindPassword attached property has been set to true
if (d == null || !GetBindPassword(d))
{
return;
}
// avoid recursive updating by ignoring the box's changed event
box.PasswordChanged -= HandlePasswordChanged;
string newPassword = (string)e.NewValue;
if (!GetUpdatingPassword(box))
{
box.Password = newPassword;
}
box.PasswordChanged += HandlePasswordChanged;
}
private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
// when the BindPassword attached property is set on a PasswordBox,
// start listening to its PasswordChanged event
PasswordBox box = dp as PasswordBox;
if (box == null)
{
return;
}
bool wasBound = (bool)(e.OldValue);
bool needToBind = (bool)(e.NewValue);
if (wasBound)
{
box.PasswordChanged -= HandlePasswordChanged;
}
if (needToBind)
{
box.PasswordChanged += HandlePasswordChanged;
}
}
private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox box = sender as PasswordBox;
// set a flag to indicate that we're updating the password
SetUpdatingPassword(box, true);
// push the new password into the BoundPassword property
SetBoundPassword(box, box.Password);
SetUpdatingPassword(box, false);
}
public static void SetBindPassword(DependencyObject dp, bool value)
{
dp.SetValue(BindPassword, value);
}
public static bool GetBindPassword(DependencyObject dp)
{
return (bool)dp.GetValue(BindPassword);
}
public static string GetBoundPassword(DependencyObject dp)
{
return (string)dp.GetValue(BoundPassword);
}
public static void SetBoundPassword(DependencyObject dp, string value)
{
dp.SetValue(BoundPassword, value);
}
private static bool GetUpdatingPassword(DependencyObject dp)
{
return (bool)dp.GetValue(UpdatingPassword);
}
private static void SetUpdatingPassword(DependencyObject dp, bool value)
{
dp.SetValue(UpdatingPassword, value);
}
}
}
I've tried cleaning, rebuilding, double-checking everything but simply won't compile. Any insight?
This class is in one of my project's folder called Utils.
If that means that the class is in the same project as the WPF application that contains the XAML, then that implies that your xmlns is probably wrong.
For the current project it should be:
xmlns:utils="clr-namespace:IPdevices.Utils;assembly="
or
xmlns:utils="clr-namespace:IPdevices.Utils"
The code can be little more simpler and readable as below
Below is the Attached property class
public static class PasswordHelper
{
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.RegisterAttached("Password",
typeof(string), typeof(PasswordHelper),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPasswordPropertyChanged));
public static readonly DependencyProperty AttachProperty =
DependencyProperty.RegisterAttached("Attach",
typeof(bool), typeof(PasswordHelper), new PropertyMetadata(false, Attach));
public static void SetAttach(DependencyObject dp, bool value)
{
dp.SetValue(AttachProperty, value);
}
public static bool GetAttach(DependencyObject dp)
{
return (bool)dp.GetValue(AttachProperty);
}
public static string GetPassword(DependencyObject dp)
{
return (string)dp.GetValue(PasswordProperty);
}
public static void SetPassword(DependencyObject dp, string value)
{
dp.SetValue(PasswordProperty, value);
}
private static void OnPasswordPropertyChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
PasswordBox passwordBox = sender as PasswordBox;
if (e.OldValue != e.NewValue)
{
if (!passwordBox.Password.Equals(e.NewValue))
{
passwordBox.Password = (string)e.NewValue;
}
}
}
private static void Attach(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
PasswordBox passwordBox = sender as PasswordBox;
if (passwordBox == null)
return;
if ((bool)e.OldValue)
{
passwordBox.PasswordChanged -= PasswordChanged;
}
if ((bool)e.NewValue)
{
passwordBox.PasswordChanged += PasswordChanged;
}
}
private static void PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox passwordBox = sender as PasswordBox;
SetPassword(passwordBox, passwordBox.Password);
}
}
Below is the XAML
<PasswordBox local:PasswordAttached.Attach="true" local:PasswordAttached.Password="{Binding Password}" Width="300" Margin="10" Height="26"/>
local is namespace of your PasswordHelper class
Below is the view Model
public class ViewModel : INotifyPropertyChanged
{
private string password;
public string Password
{
get
{
return password;
}
set
{
password = value;
if(PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Password"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I am implementing an attached behavior in a WPF application. I need to pass a type parameters to the behavior, so I can call a method void NewRow(Table<T> table) on SqliteBoundRow. If I was instantiating an object in XAML, I would pass a type parameters using x:TypeArguments, but I don't see a way to do this when setting an attached behavior, because it uses a static property.
Code for the attached behavior looks like this:
public abstract class SqliteBoundRow<T> where T : SqliteBoundRow<T>
{
public abstract void NewRow(Table<T> table);
}
public class DataGridBehavior<T> where T:SqliteBoundRow<T>
{
public static readonly DependencyProperty IsEnabledProperty;
static DataGridBehavior()
{
IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool), typeof(DataGridBehavior<T>),
new FrameworkPropertyMetadata(false, OnBehaviorEnabled));
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
private static void OnBehaviorEnabled(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var dg = dependencyObject as DataGrid;
dg.InitializingNewItem += DataGrid_InitializingNewItem;
}
private static void DataGrid_InitializingNewItem(object sender,
InitializingNewItemEventArgs e)
{
var table = (sender as DataGrid).ItemsSource as Table<T>;
(e.NewItem as T).NewRow(table);
}
}
XAML looks like this:
<DataGrid DataGridBehavior.IsEnabled="True">
<!-- DataGridBehavior needs a type parameter -->
</DataGrid>
My current solution is to wrap DataGridBehavior in a a derived class which specifies the type parameters.
The simplest solution is for you to declare another Attached Property, but of type Type to hold the parameter value for you. In this case, you would set the Type property before your IsEnabled Attached Property:
<DataGrid DataGridBehavior.TypeParameter="{x:Type SomePrefix:SomeType}"
DataGridBehavior.IsEnabled="True" ... />
Looking again at your code, it seems as though your IsEnabled property does nothing except adding a new row to your table... in that case, there's no reason why you couldn't replace it with the TypeParameter Attached Property and use that one to add the new row instead.
I don't think WPF provides an elegant syntactic way to do what you want. So I was just about to post a similar answer to the one from Sheridan. That is you can provide an additional property of type Type to determine the generic type. However, Sheridan beat me to it. Below is some sample code of how you can do this with reflection:
Xaml
<DataGrid behaviors:DataGridBehavior.InnerType="namespace:SqliteBoundRow"
behaviors:DataGridBehavior.IsEnabled="True">
</DataGrid>
Code behind
public abstract class DataGridBehavior
{
private static readonly ConcurrentDictionary<Type, DataGridBehavior> Behaviors = new ConcurrentDictionary<Type, DataGridBehavior>();
public static readonly DependencyProperty IsEnabledProperty;
public static readonly DependencyProperty InnerTypeProperty;
static DataGridBehavior()
{
IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool), typeof(DataGridBehavior),
new FrameworkPropertyMetadata(false, OnBehaviorEnabled));
InnerTypeProperty = DependencyProperty.RegisterAttached("InnerType",
typeof(Type), typeof(DataGridBehavior));
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetInnerType(DependencyObject obj, Type value)
{
obj.SetValue(InnerTypeProperty, value);
}
public static Type GetInnerType(DependencyObject obj)
{
return (Type)obj.GetValue(InnerTypeProperty);
}
private static void OnBehaviorEnabled(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var innerType = GetInnerType(dependencyObject);
if (innerType == null)
throw new Exception("Missing inner type");
var behavior = Behaviors.GetOrAdd(innerType, GetBehavior);
behavior.OnEnabled(dependencyObject);
}
private static DataGridBehavior GetBehavior(Type innerType)
{
var behaviorType = typeof(DataGridBehavior<>).MakeGenericType(innerType);
var behavior = (DataGridBehavior)Activator.CreateInstance(behaviorType);
return behavior;
}
protected abstract void OnEnabled(DependencyObject dependencyObject);
}
public class DataGridBehavior<T> : DataGridBehavior
where T : SqliteBoundRow
{
protected override void OnEnabled(DependencyObject dependencyObject)
{
//dg.InitializingNewItem += DataGrid_InitializingNewItem;
}
private static void DataGrid_InitializingNewItem(object sender,
InitializingNewItemEventArgs e)
{
//var table = (sender as DataGrid).ItemsSource as Table<T>;
//(e.NewItem as T).NewRow(table);
}
}
public class SqliteBoundRow
{
}
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...
I created my own DataGrid control which inherits from DataGrid. I declared a Dependency Property which I want to use at column level, so on the PreviewKeyDown event I check the value and decide if this current cell needs to be handled or not.
public class MyDataGrid : DataGrid
{
public static DependencyProperty HandleKeyPressEventProperty =
DependencyProperty.RegisterAttached(
"HandleKeyPressEvent",
typeof(bool),
typeof(MyDataGrid),
new FrameworkPropertyMetadata(true));
public bool HandleKeyPressEvent
{
get { return (bool)GetValue(HandleKeyPressEventProperty); }
set { SetValue(HandleKeyPressEventProperty, value); }
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (HandleKeyPressEvent)
{
HandleKeyPress(e);
}
else
{
base.OnPreviewKeyDown(e);
}
}
}
My XAML looks like this:
<MyDataGrid x:Name="myDataGrid">
<DataGridTextColumn MyDataGrid.HandleKeyPressEvent = "True" />
<DataGridTemplateColumn MyDataGrid.HandleKeyPressEvent = "False"/>
</MyDataGrid>
But I am having a real problem to have this dependency property available at the column level. What I try to do is just like Grid.Column. Can someone help me with that?
An attached property has a static Get method and a static Set method (which are declared by the property name prefixed by Get/Set) instead of a CLR Property wrapper. To check the current column in OnPreviewKeyDown, you can use CurrentCell.Column
public class MyDataGrid : DataGrid
{
public static readonly DependencyProperty HandleKeyPressEventProperty =
DependencyProperty.RegisterAttached("HandleKeyPressEvent",
typeof(bool),
typeof(MyDataGrid),
new UIPropertyMetadata(true));
public static bool GetHandleKeyPressEvent(DependencyObject obj)
{
return (bool)obj.GetValue(HandleKeyPressEventProperty);
}
public static void SetHandleKeyPressEvent(DependencyObject obj, bool value)
{
obj.SetValue(HandleKeyPressEventProperty, value);
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (GetHandleKeyPressEvent(CurrentCell.Column) == true)
{
HandleKeyPress(e);
}
else
{
base.OnPreviewKeyDown(e);
}
}
}