DependencyProperty callback not called - c#

I'm currently doing an usercontrol with C#/WPF and i'm using some DependencyProperty objects.
What I want to do is when the value changes, we call a callback method to process some data... I saw that there is a PropertyChangedCallback class for this purpose, but it doesn't work..
Here's my code:
UserControl:
public partial class TimeLine : UserControl
{
public static readonly DependencyProperty FramecountProperty = DependencyProperty.Register("FrameCount", typeof(Int32), typeof(TimeLine), new FrameworkPropertyMetadata(0, new PropertyChangedCallback(FrameCountChanged)));
public Int32 FrameCount
{
get { return (Int32)this.GetValue(FramecountProperty); }
set { this.SetValue(FramecountProperty, value); }
}
// More code...
public static void FrameCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Do stuff
}
}
xaml:
<!-- Time line container -->
<controls:TimeLine Grid.Row="2" Header="Storyboard" FrameCount="{Binding FrameCount}" />
ViewModel:
private Int32 frameCount;
public Int32 FrameCount
{
get { return this.frameCount; }
// this is from: https://github.com/ShyroFR/CSharp-Elegant-MVVM
set { this.NotifyPropertyChanged(ref this.frameCount, value); }
}
public MainViewModel()
{
this.FrameCount = 42;
}
I'm I doing it the wrong way?
Thanks for your help.

I've found a solution, by finding the ancestor.
<controls:TimeLine Grid.Row="2" Header="Storyboard" FrameCount="{Binding Path=DataContext.FrameCount, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" />
Thanks for your help!

Add Mode=TwoWay to your binding. By default binding for custom dependency properties are OneWay.

Related

WPF - How to keep Dependency Property and View Model property in sync?

In WPF I have been trying to figure out how to keep a views dependency property and one of it's view model's properties in sync for a while now without any luck. I have done a fair amount research into the subject but none of the suggested solutions are working for me and I was hoping someone could help me find what I am missing.
I attempted many of the things suggested in this post, Twoway-bind view's DependencyProperty to viewmodel's property?, because of all the things I read it looked to be the most promising, but was never able to get the results I was looking for.
I have written a simple program to demonstrate that issue I am having. In it I set the the property IntValue in MainWindowViewModel to 2 and then Bind it to a dependency property created in the UserControl IncrementIntView. Then when I push the button in IncrementIntView it increases the value of IntValue by one. This all works fine inside the UserControl IncrementIntView but I can't figure out how to send the updated IntValue back to MainWindowViewModel, it stays set to 2.
IncrementIntView.xaml.cs
public partial class IncrementIntView : UserControl
{
public int IntValue
{
get { return (int)GetValue(IntValueProperty); }
set { SetValue(IntValueProperty, value); }
}
public static readonly DependencyProperty IntValueProperty =
DependencyProperty.Register("IntValue", typeof(int), typeof(IncrementIntView),
new PropertyMetadata(-1, new PropertyChangedCallback(IntValueChanged)));
private static void IntValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
IncrementIntView detailGroup = dependencyObject as IncrementIntView;
if (e.NewValue != null)
{
detailGroup.ViewModel.IntValue = (int)e.NewValue;
}
}
public IncrementIntView()
{
InitializeComponent();
}
}
IncrementIntViewModel.cs
public class IncrementIntViewModel : ViewModelBase
{
private int intValue;
public int IntValue
{
get { return intValue; }
set { SetProperty(ref intValue, value); }
}
public IncrementIntViewModel()
{
incrementIntCommand = new Command(IncrementInt);
}
private Command incrementIntCommand;
public Command IncrementIntCommand { get { return incrementIntCommand; } }
public void IncrementInt()
{
IntValue++;
}
}
IncrementIntView.xaml
<UserControl.DataContext>
<local:IncrementIntViewModel x:Name="ViewModel" />
</UserControl.DataContext>
<Grid>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding IntValue}" />
<Button Content="Increment" Command="{Binding IncrementIntCommand}" Width="75" />
</StackPanel>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
private int intValue = 2;
public int IntValue
{
get { return intValue; }
set { SetProperty(ref intValue, value); }
}
}
MainWindow.xaml
<Window.DataContext>
<local:MainWindowViewModel x:Name="ViewModel"/>
</Window.DataContext>
<Grid>
<StackPanel Margin="10">
<local:IncrementIntView IntValue="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=ViewModel}" />
<Label Content="{Binding IntValue}" />
</StackPanel>
</Grid>
I can see that your code is passing the IntValue from MainWindowViewModel's property to IncrementIntView's dependency property to IncrementIntViewModel's property.
The increment button is updating the IncrementIntViewModel's IntValue property. Unfortunately, whatever happens in the IncrementIntViewModel is not being reflected back to the IncrementIntView's IntValue dependency property. The TwoWay Mode is not between IncrementIntView's dependency property and IncrementIntViewModel's property, but it is between MainWindowViewModel's property to IncrementIntView's dependency property.
The easy solution: Bind the MainWindow's Label to IncrementIntViewModel's IntValue property without bothering the View's property.
<local:IncrementIntView x:Name="iiv" IntValue="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=ViewModel}" />
<Label Content="{Binding DataContext.IntValue, ElementName=iiv}" />
<!--You need to specify DataContext.IntValue, because you have same name for both view's dependency property and viewmodel's property-->
Here you can see that MainWindowViewModel's IntValue is not that important, because it just passes the value to IncrementIntViewModel once and never have the value updated ever.
The other solution: You need to trigger value change back to MainViewModel's property.
First thing first, there is no connection between MainViewModel and IncrementIntViewModel. One solution is to make the MainViewModel to be singleton, so that when increment is done inside the IncrementIntViewModel, you want to update the MainViewModel's property as well.
In MainViewModel.cs
public static MainWindowViewModel SingletonInstance { get; set; }
public MainWindowViewModel()
{
if (SingletonInstance == null)
{
SingletonInstance = this;
}
}
In IncrementIntViewModel.cs
public void IncrementInt()
{
IntValue++;
MainWindowViewModel.SingletonInstance.IntValue = IntValue;
}
The other other solution: Similar to the above solution, but we don't need to make Singleton instance of MainWindowViewModel, because MainWindow is singleton to begin with.
In IncrementIntViewModel.cs
public void IncrementInt()
{
IntValue++;
((MainWindowViewModel)App.Current.MainWindow.DataContext).IntValue = IntValue;
}
If your intention is to update the IntValue from IncrementViewModel's property to IncrementView's dependency property, then you might ask why you need to do this, because MVVM is supposed to separate between V and VM. V should be looking to VM, but not the other way around.

XAML TextBox isReadOnly Binding

I am trying to make a textbox read only using Binding in Windows 8.1 apps. I have tried some code from the internet which does not work.
Can you suggest any simplest way to do it, I am very new to the concept Binding.
XAML
<TextBox x:Name="tbOne" IsReadOnly="{Binding Path=setread, Mode=OneWay}" />
<Button Content="isReadonlyBinding" x:Name="isReadonlyBinding" Click="isReadonlyBinding_Click"></Button>
XAML.CS
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(
"setread",
typeof(bool),
typeof(MainPage),
new PropertyMetadata(false)
);
public bool setread
{
get { return (bool)GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
private void isReadonlyBinding_Click(object sender, RoutedEventArgs e)
{
setread = true;
}
try this.
<page X:name="PageName">
IsReadOnly="{Binding ElementName=PageName,Path=setread, Mode=OneWay}"
Implement INotifyPropertyChanged on your code behind. Then modify the property as follows:
private bool _setread;
public bool Setread
{
get { return _setread; }
set {
if(_seatread == value) return;
_setread = value;
RaisePropertyChanged("Setread");
}
}
Give a name to root element like x:Name="root", and bind to Setread with ElementName=page. Note that it is much better to prepare a view model. A view-model-code-behind is just a quick workaround.

How do I access a text bound to a dependency property in the view model?

I would like to access the dependency property bound to a text box in the view model.
In the view model I have the following code:
Answer _Answer = new Answer();
_Answer.GetValue(Answer.Deutsch1AnswerProperty);
Console.WriteLine(_Answer.Deutsch1Answer);
The dependency property is defined as follows:
public class Answer : DependencyObject
{
// Ereignis
public event EventHandler Deutsch1AnswerChanged;
public static readonly DependencyProperty Deutsch1AnswerProperty;
public String Deutsch1Answer
{
get { return (String)GetValue(Deutsch1AnswerProperty); }
set { SetValue(Deutsch1AnswerProperty, value); }
}
public static void OnDeutsch1AnswerChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Answer _Answer = (Answer)sender;
if (_Answer.Deutsch1AnswerChanged != null)
_Answer.Deutsch1AnswerChanged(_Answer, null);
}
static Answer()
{
FrameworkPropertyMetadata meta = new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
meta.PropertyChangedCallback = new PropertyChangedCallback(OnDeutsch1AnswerChanged);
Deutsch1AnswerProperty = DependencyProperty.Register("Deutsch1Answer", typeof(String), typeof(Answer), meta);
}
}
In the MainWindow.Xaml I have the follwoing code:
<Window.Resources>
<local:Answer x:Key="_Answer" Deutsch1Answer=string.Empty />
</Window.Resources>
<Grid Definitions ...>
<TextBox Grid.Column="1" Grid.Row="2" Name="BoxDeutsch1" Text="{Binding Source={StaticResource _Answer}, Path=Deutsch1Answer}">
</TextBox>
I cannot access the Text Property in the view model. Please help.
The view model looks like this:
public class VokabelViewModel : INotifyPropertyChanged
{
private Vokabel _Model;
public VokabelViewModel(Vokabel model)
{
_Model = model;
}
private ICommand _boxDeutsch1_HitEnter;
public ICommand BoxDeutsch1_HitEnter
{
get
{
return _boxDeutsch1_HitEnter ?? (_boxDeutsch1_HitEnter = new CommandHandler(() => MyActionBoxDeutsch1_HitEnter(), _canExecuteBoxDeutsch1_HitEnter));
}
}
private bool _canExecuteBoxDeutsch1_HitEnter;
public void MyActionBoxDeutsch1_HitEnter()
{
Answer _Answer = new Answer();
_Answer.GetValue(Answer.Deutsch1AnswerProperty);
_Model.TestVokabel(_Answer.Deutsch1Answer);
}
If you want to access the Deutsch1Answer DependencyProperty in your ViewModel you should bind to it.
However I dont really see the point in why you've got the "Answer"-class.
If you want to bind the textboxs text to a property in your view model you should add an Answer property and bind it directly to the textbox.
In your ViewModel:
private string m_answer;
public string Answer
{
get { return m_answer; }
set
{
m_answer = value;
OnPropertyChanged("Answer");
}
}
In your View:
...
<TextBox Grid.Column="1" Grid.Row="2" Name="BoxDeutsch1" Text="{Binding Answer}">
...

"AttachedProperty" PropertyChangedCallback never calls for my LayoutAnchorable, but works on DockingManager. AvalonDock

I am trying to use AttachedProperty in my AvalonDock, I want it to be part of LayoutAnchorable but PropertyChangedCallback never get called. i have binded AttachedPropert and i am getting the control over ViewModel ie: when binded property changes it trigger my ViewModel Property.
My AttachedProperty
public static readonly DependencyProperty IsCanVisibleProperty =
DependencyProperty.RegisterAttached("IsCanVisible", typeof(bool), typeof(AvalonDockBehaviour), new FrameworkPropertyMetadata(new PropertyChangedCallback(IsCanVisiblePropertyChanged)));
private static void IsCanVisiblePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LayoutAnchorable control = d as LayoutAnchorable;
if (control != null)
{
control.IsVisible = (bool)e.NewValue;
}
}
public static void SetIsCanVisible(DependencyObject element, bool value)
{
element.SetValue(IsCanVisibleProperty, value);
}
public static bool GetIsCanVisible(DependencyObject element)
{
return (bool)element.GetValue(IsCanVisibleProperty);
}
XAML
<xcad:DockingManager>
<xcad:LayoutRoot >
<xcad:LayoutPanel Orientation="Horizontal" >
<xcad:LayoutAnchorablePane >
<xcad:LayoutAnchorable Title="Folder" behv:AvalonDockBehaviour.IsCanVisible="{Binding IsHideExplorer, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<Views:ExplorerView DataContext="{Binding ExplorerViewModel}"/>
</xcad:LayoutAnchorable>
</xcad:LayoutAnchorablePane>
</xcad:LayoutPanel>
</xcad:LayoutRoot>
</xcad:DockingManager>
ViewModel Property
private bool _IsHideExplorer;
public bool IsHideExplorer
{
get { return _IsHideExplorer; }
set { _IsHideExplorer = value; NotifyPropertyChanged(); }
}
I have tried attaching the property to DockingManager the PropertyChangedCallback works. Any Help guys.
Did you already check the DataContext of your LayoutAnchorable? Maybe the DataContext is not passed down to it. In that case the Binding would not work and your DependencyProperty is not updated.

Create a proxy for a dependency property

I am trying to create a simple dependency property proxy. I made a custom control, it's a file picker, which is made off a textbox (name: "TextBox_FilePath") and a button showing the open file dialog.
As I am making a reusable control I'd like it to have a "SelectedFilePath" property. As the Text property seems to be perfect for my control to be the "SelectedFilePath" property, I'd just like to proxy these dependency property.
The first approach I made was:
public static readonly DependencyProperty SelectedFilePathProperty = TextBox.TextProperty;
public string SelectedFilePath
{
get { return (string) this.TextBox_FilePath.GetValue(SelectedFilePathProperty); }
set { this.TextBox_FilePath.SetValue(SelectedFilePathProperty, value); }
}
which worked, but throwed an exception when trying to bind to that property. Then I came off with:
public static readonly DependencyProperty SelectedFilePathProperty =
DependencyProperty.Register("SelectedFilePath", typeof (string), typeof (FilePicker), new PropertyMetadata(default(string)));
public string SelectedFilePath
{
get { return (string) this.TextBox_FilePath.GetValue(SelectedFilePathProperty); }
set { this.TextBox_FilePath.SetValue(SelectedFilePathProperty, value); }
}
which does work, but I've got no idea why?! Where did I specify I wanted the text property of the textbox?
What am I missing to simply proxy out that dependency property?
EDIT:
The solution with AddOwner doesn't work too, it throws an Excetion saying "binding can only be applied on a dependency property". Code:
public static readonly DependencyProperty SelectedFilePathProperty =
TextBox.TextProperty.AddOwner(typeof(FilePicker));
public string SelectedFilePath
{
get { return (string)this.TextBox_FilePath.GetValue(SelectedFilePathProperty); }
set { this.TextBox_FilePath.SetValue(SelectedFilePathProperty, value); }
}
What don't I understand?
EDIT2:
For everybody else having issues understanding the answer, I've made a little graphic
The first approach does not work because the property is registered only for the TextBox, adding a reference in another class does nothing.
The second one just creates a whole new string property.
If you really want to reuse the TextBox.TextProperty call AddOwner on it.
e.g.
public static readonly DependencyProperty SelectedFilePathProperty =
TextBox.TextProperty.AddOwner(typeof(FilePicker));
(Note that this property is registered as "Text", so you probably should just create a new property with the name you want as you did already. I would also recommend to set metadata flags to bind two-way by default if you want to have the same binding behaviour as TextBox.Text.)
This solution is a little tricky but works.
Given this user control:
<Grid>
<StackPanel>
<WpfApplication1:FilePicker SelectedFilePath ="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding MyProperty}" />
</StackPanel>
</Grid>
And its viewmodel:
public class MainWindowViewModel : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string e)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(e));
}
#endregion
private string _myProperty;
public string MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
OnPropertyChanged("MyProperty");
}
}
}
XAML for FilePicker control:
<Grid>
<TextBox x:Name="TextBox_FilePath" DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type WpfApplication1:FilePicker}}}" Text="{Binding SelectedFilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
CodeBehind for FilePicker control:
public partial class FilePicker : UserControl
{
public FilePicker()
{
InitializeComponent();
}
/* private PROXY DP*/
private static readonly DependencyProperty TextProperty =
TextBox.TextProperty.AddOwner(typeof(FilePicker));
/* public DP that will fire getter/setter for private DP */
public static readonly DependencyProperty SelectedFilePathProperty =
DependencyProperty.Register("SelectedFilePath", typeof(string), typeof(FilePicker), new PropertyMetadata(default(string)));
public string SelectedFilePath
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
Works like a charm.
As I had issues understanding H.B.s answer I made a little graphic which helped me to understand what's going on under the hood. Here it is;
Maybe it helps someone else :)

Categories

Resources