I have a user control that is having issues binding to a dependency property for IsEnabled. I have also tried manually setting the IsEnabled="false" and that also appears to be not working.
Here is the code:
public partial class News : UserControl
{
public static readonly DependencyProperty IsAuthenticatedProperty =
DependencyProperty.Register(
"IsAuthenticated",
typeof(bool),
typeof(News),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(ChangeAuth)));
public bool IsAuthenticated
{
get
{
return (bool) GetValue(IsAuthenticatedProperty);
}
set
{
SetValue(IsAuthenticatedProperty, value);
}
}
private static void ChangeAuth(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool == false)
{
(source as News).UpdateAuth(false);
}
else
{
(source as News).UpdateAuth(true);
}
}
private void UpdateAuth(bool value)
{
IsAuthenticated = value;
}
public News()
{
IsAuthenticated = false;
this.IsEnabled = false;
InitializeComponent();
}
Any Ideas ? Thanks In Advance
It's hard to be certain since you haven't shown your binding in XAML, however, your binding will by default be looking for the bound property on whatever is set in the DataContext. I suspect this is the problem...
If this assumption is correct, a similar solution is presented over here...
Related
It works when I bind directly to EventsSource but it doesn't when I change the binding to EventsView.
// EventsControl class
private bool Filter(object obj)
{
if (!(obj is Event #event)) return false;
if (string.IsNullOrEmpty(Location)) return true;
return true;
// return #event.Location == Location;
}
public static readonly DependencyProperty EventsSourceProperty = DependencyProperty.Register(
nameof(EventsSource), typeof(ObservableCollection<Event>), typeof(EventsControl), new PropertyMetadata(default(ObservableCollection<Event>), EventsSourceChanged));
public ObservableCollection<Event> EventsSource
{
get => (ObservableCollection<Event>)GetValue(EventsSourceProperty);
set => SetValue(EventsSourceProperty, value);
}
public ICollectionView EventsView { get; set; }
private static void EventsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is EventsControl eventsControl)) return;
var view = new CollectionViewSource { Source = e.NewValue }.View;
view.Filter += eventsControl.Filter;
eventsControl.EventsView = view;
//view.Refresh();
}
What could be wrong with this code?
I don't want to use a default view (
WPF CollectionViewSource Multiple Views? )
I made it a dependency property and it works. Not sure if that's the best way to solve it though.
public static readonly DependencyProperty EventsViewProperty = DependencyProperty.Register(
nameof(EventsView), typeof(ICollectionView), typeof(EventsControl), new PropertyMetadata(default(ICollectionView)));
public ICollectionView EventsView
{
get => (ICollectionView) GetValue(EventsViewProperty);
set => SetValue(EventsViewProperty, value);
}
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
}
}
}
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...
Sorry to be cliche... but I'm pretty new to WPF and MVVM so I'm not sure how to handle this properly. I have a WinForms control within one of my views that I need to modify in it's code behind when an event is raised in the ViewModel. My view's datacontext is inherited so the viewmodel is not defined in the views constructor. How would I go about properly handling this? I am not using any frameworks with built in messengers or aggregators. My relevant code is below. I need to fire the ChangeUrl method from my ViewModel.
EDIT: Based on the suggestion from HighCore, I have updated my code. I am still not able to execute the ChangeUrl method however, the event is being raised in my ViewModel. What modifications need to be made??
UserControl.xaml
<UserControl ...>
<WindowsFormsHost>
<vlc:AxVLCPlugin2 x:Name="VlcPlayerObject" />
</WindowsFormsHost>
</UserControl>
UserControl.cs
public partial class VlcPlayer : UserControl
{
public VlcPlayer()
{
InitializeComponent();
}
public string VlcUrl
{
get { return (string)GetValue(VlcUrlProperty); }
set
{
ChangeVlcUrl(value);
SetValue(VlcUrlProperty, value);
}
}
public static readonly DependencyProperty VlcUrlProperty =
DependencyProperty.Register("VlcUrl", typeof(string), typeof(VlcPlayer), new PropertyMetadata(null));
private void ChangeVlcUrl(string newUrl)
{
//do stuff here
}
}
view.xaml
<wuc:VlcPlayer VlcUrl="{Binding Path=ScreenVlcUrl}" />
ViewModel
private string screenVlcUrl;
public string ScreenVlcUrl
{
get { return screenVlcUrl; }
set
{
screenVlcUrl = value;
RaisePropertyChangedEvent("ScreenVlcUrl");
}
}
WPF does not execute your property setter when you Bind the property, instead you must define a Callback method in the DependencyProperty declaration:
public string VlcUrl
{
get { return (string)GetValue(VlcUrlProperty); }
set { SetValue(VlcUrlProperty, value); }
}
public static readonly DependencyProperty VlcUrlProperty =
DependencyProperty.Register("VlcUrl", typeof(string), typeof(VlcPlayer), new PropertyMetadata(null, OnVlcUrlChanged));
private static void OnVlcUrlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var player = obj as VlcPlayer;
if (obj == null)
return;
obj.ChangeVlcUrl(e.NewValue);
}
private void ChangeVlcUrl(string newUrl)
{
//do stuff here
}
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);
}
}
}