DependencyProperty not updated - c#

I have a simple WPF window with a TabItem containing a ComboBox with colors and a custom Canvas drawing a rectangle with that color. In my PaintCanvas I have a DependencyProperty like this:
class PaintCanvas : System.Windows.Controls.Canvas
{
public static readonly DependencyProperty PaintObjectProperty = DependencyProperty.Register(
"PaintObject", typeof(PaintObject), typeof(PaintCanvas), new PropertyMetadata(OnPaintObjectChanged));
public PaintObject PaintObject
{
get { return this.GetValue(PaintObjectProperty) as PaintObject; }
set
{
this.SetValue(PaintObjectProperty, value);
}
}
private static void OnPaintObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PaintCanvas canvas = (PaintCanvas)d;
// Update stuff
canvas.InvalidateVisual();
}
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
if (PaintObject != null)
{
dc.DrawRectangle(new SolidColorBrush(PaintObject.Color), null, new Rect(0, 0, PaintObject.Width, PaintObject.Height));
}
}
}
The PaintObject dependency property is bound in xaml to its corresponding property in PaintViewModel:
<TabControl>
<TabItem DataContext="{Binding PaintViewModel}">
<StackPanel >
<ComboBox ItemsSource="{Binding Colors}" SelectedItem="{Binding Color}" />
<my:PaintCanvas Width="100" Height="100" PaintObject="{Binding PaintObject}" />
</StackPanel>
</TabItem>
</TabControl>
PaintViewModel is a property in the Window's ViewModel:
class MainViewModel
{
PaintViewModel paintViewModel;
public MainViewModel()
{
paintViewModel = new PaintViewModel();
}
public PaintViewModel PaintViewModel
{
get { return paintViewModel; }
}
...
}
The actual PaintViewModel:
class PaintViewModel : INotifyPropertyChanged
{
PaintObject paintObject;
ObservableCollection<Color> colors = new ObservableCollection<Color>();
Color currentColor;
public PaintObject PaintObject
{
get { return paintObject; }
set { paintObject = value; RaisePropertyChanged("PaintObject"); }
}
public ObservableCollection<Color> Colors
{
get { return colors; }
}
public Color Color
{
get { return currentColor; }
set {
currentColor = value;
RaisePropertyChanged("Color");
paintObject.Color = currentColor;
RaisePropertyChanged("PaintObject");
}
}
// Constructors and INotifyPropertyChanged stuff...
}
The TabItem seems to be correctly bound to the view model, because the color combobox works as it should. However, although the paint object is updated and RaisePropertyChanged("PaintObject") is called, the DependencyProperty in PaintCanvas is never updated. What am I doing wrong here??

I don't see that you change reference to PaintObject, you cahanged one of the properties of it(Color) and fire as PaintObject is changed, and since it is not, dependency property doesnt refresh
As a solution, you can add Color dependency property in the PaintCanvas, and bind Color to PaintObject.Color in xaml
<my:PaintCanvas Width="100" Height="100" PaintObject="{Binding PaintObject}" Color={Binding PaintObject.Color} />
And if you not forget to call NotifyPropertyChanged of Color property in PaintObject, PaintConvas Color property will be fired to be changed
I see some mess in your design, try to keep things simple

Related

Listview item background color based on property (WPF / MVVM)

I would like to display a title and a color (see the picture - replace 14474460 with the corresponding color) within a ListView
The color will be picked by the colorpicker (see "OrangeRed"). So everything works, SQLite database is updated, the number changes correspondingly but I can't change the background.
my XAML
<GridViewColumn Header="Color">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Color}" Background="{Binding ColorBackground}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
my Color property (bound to the ColorPicker - stores the color as System.Windows.Media.Color)
public Color Color
{
get { return color; }
set
{
color = value;
NotifyPropertyChanged("Color");
}
}
my background color property (bound to the textblock - guess I need a brush here, right?)
public Brush ColorBackground
{
get { return colorbackground; }
set
{
colorbackground = new SolidColorBrush(Color);
NotifyPropertyChanged("ColorBackground");
}
}
my LeaveType entity
using Dapper.Contrib.Extensions;
using System.ComponentModel;
using System.Windows.Media;
namespace Calendar.Database.Entities
{
[Dapper.Contrib.Extensions.Table("LeaveTypes")]
public class LeaveTypeEntity : Entity, INotifyPropertyChanged
{
private long color;
public long Color
{
get
{
return color;
}
set
{
color = value;
NotifyPropertyChanged("Color");
}
}
private string type;
public string Type
{
get
{
return type;
}
set
{
type = value;
NotifyPropertyChanged("Type");
}
}
private Brush colorbackground;
[Computed]
public Brush ColorBackground
{
get { return colorbackground; }
set
{
colorbackground = value;
NotifyPropertyChanged("ColorBackground");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
My helper messagebox (right after the update method) return a HEX code for ColorBackground, so I guess that isn't the problem.
Can anyone help?
UPDATE
ColorBackground property (ViewModel file)
public Brush ColorBackground { get; set; }
Color property (ViewModel file)
private Color color;
public Color Color
{
get { return color; }
set
{
color = value;
ColorBackground = new SolidColorBrush(Color);
NotifyPropertyChanged("Color");
}
}

How to properly expose properties of internal controls?

I have a custom control named CustomTreeView which contains a TreeView. I would like to expose the SelectedItem Property of the TreeView to users of the custom control.
For that I tried to add a new dependency property to the custom control and bind that property to the SelectedItem Property of the TreeView.
Unfortunatly I seem to be getting it wrong. Could you take a look?
TreeView.xaml
<UserControl x:Class="Fis.UI.Windows.BacNet.Views.RestructuredView.View.Controls.CustomTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Selected="{Binding ElementName=treeView, Path=SelectedItem}">
<TreeView x:Name="treeView"/>
</UserControl>
TreeView.xaml.cs
public partial class CustomTreeView : UserControl
{
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(
"Selected", typeof(Node),
typeof(TreeView)
);
public Node Selected
{
get { return (Node)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
public TreeView()
{
InitializeComponent();
}
}
Thanks!
Part of your solution is in this answer here. It's a link-only answer with a dead link (or was -- I just improved it), but it does mention the key point.
You can't bind TreeView.SelectedItem. Your dependency property definition is broken in multiple ways and it should be named SelectedItem in accordance with standard WPF practice. Here's the usercontrol code behind, which defines the dependency property along with event handlers as a substitute for the binding.
public partial class CustomTreeView : UserControl
{
public CustomTreeView()
{
InitializeComponent();
}
#region SelectedItem Property
public Node SelectedItem
{
get { return (Node)GetValue(SelectedProperty); }
set { SetValue(SelectedProperty, value); }
}
public static readonly DependencyProperty SelectedProperty =
DependencyProperty.Register(nameof(SelectedItem), typeof(Node), typeof(CustomTreeView),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
Selected_PropertyChanged)
{ DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
protected static void Selected_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as CustomTreeView).OnSelectedChanged(e.OldValue);
}
private void OnSelectedChanged(object oldValue)
{
if (SelectedItem != treeView.SelectedItem)
{
var tvi = treeView.ItemContainerGenerator.ContainerFromItem(SelectedItem) as TreeViewItem;
if (tvi != null)
{
tvi.IsSelected = true;
}
}
}
#endregion SelectedItem Property
private void treeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (SelectedItem != e.NewValue)
{
SelectedItem = e.NewValue as Node;
}
}
}
And here's the TreeView in the UserControl XAML. I'm omitting ItemsSource and ItemTemplate as irrelevant.
<TreeView
x:Name="treeView"
SelectedItemChanged="treeView_SelectedItemChanged"
/>
And here's the snippet from MainWindow.xaml I used for testing it:
<StackPanel>
<local:CustomTreeView x:Name="treeControl" />
<Label
Content="{Binding SelectedItem.Text, ElementName=treeControl}"
/>
</StackPanel>
My Node class has a Text property and I'm populating the tree via the DataContext to save having to set up another dependency property for the items.

"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.

Windows Store App - XAML - Bind two values for Height Property

I want to bind the height of a control the sum of two other heights, so the UI looks nice various screen sizes.
<GridView
AutomationProperties.AutomationId="ItemDetails"
ItemsSource="{Binding data}"
IsSwipeEnabled="False"
SelectionMode="None" Height="{Binding Height, (ElementName=item - ElementName=itemTitle)}" >
<GridView.ItemTemplate>
<DataTemplate>
<dll:TaskItemControl/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
The above XAML is invalid, but it demonstrates what I want to do. I have two elements, item and itemTitle. item is a ScrollView that gets set to the height of the screen and I want the GridView to be the same height as the ScrollView minus the height of the itemTitle.
Is there a way to do this in XAML?
Note: The reasons for doing this are beyond the scope of this question. So please don't comment about restricting the height of a control within a ScrollView.
This can be easily done in code behind by subscribing the SizeChanged events of the two elements and update the Height of the GridView whenever the handlers are called.
But you want a pure XAML solution, and this is where Behaviors come into play.
Use a Behavior.
First you need to add Blend SDK reference to your project.
Then you need to create a new class that implements IBehavior. This class needs three dependency properties just to reference the GridView, the item and the itemTitle. So you can subscribe to their SizeChanged events and calulate the Height accordingly.
I choose to attach this behavior to a top level Panel (most likely your LayoutRoot Grid) because I want to ensure that all the elements under it are rendered properly inside its Loaded event handler.
The full Behavior class would look something like this -
public class HeightBehavior : DependencyObject, IBehavior
{
public GridView GridView
{
get { return (GridView)GetValue(GridViewProperty); }
set { SetValue(GridViewProperty, value); }
}
public static readonly DependencyProperty GridViewProperty =
DependencyProperty.Register("GridView", typeof(GridView), typeof(HeightBehavior), new PropertyMetadata(null));
public FrameworkElement FirstItem
{
get { return (FrameworkElement)GetValue(FirstItemProperty); }
set { SetValue(FirstItemProperty, value); }
}
public static readonly DependencyProperty FirstItemProperty =
DependencyProperty.Register("FirstItem", typeof(FrameworkElement), typeof(HeightBehavior), new PropertyMetadata(null));
public FrameworkElement SecondItem
{
get { return (FrameworkElement)GetValue(SecondItemProperty); }
set { SetValue(SecondItemProperty, value); }
}
public static readonly DependencyProperty SecondItemProperty =
DependencyProperty.Register("SecondItem", typeof(FrameworkElement), typeof(HeightBehavior), new PropertyMetadata(null));
public DependencyObject AssociatedObject { get; set; }
public void Attach(DependencyObject associatedObject)
{
this.AssociatedObject = associatedObject;
var control = (Panel)this.AssociatedObject;
control.Loaded += AssociatedObject_Loaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
this.FirstItem.SizeChanged += FirstItem_SizeChanged;
this.SecondItem.SizeChanged += SecondItem_SizeChanged;
// force to re-calculate the Height
this.FirstItem.Width += 0.5;
}
private void FirstItem_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.SetAssociatedObjectsHeight();
}
private void SecondItem_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.SetAssociatedObjectsHeight();
}
private void SetAssociatedObjectsHeight()
{
this.GridView.Height = this.FirstItem.ActualHeight - this.SecondItem.ActualHeight;
}
public void Detach()
{
this.FirstItem.SizeChanged -= FirstItem_SizeChanged;
this.SecondItem.SizeChanged -= SecondItem_SizeChanged;
var control = (Panel)this.AssociatedObject;
control.Loaded -= AssociatedObject_Loaded;
}
}
Then in my XAML, I attach it to my top level Grid, like this.
<Grid x:Name="LayoutRoot">
<Interactivity:Interaction.Behaviors>
<local:HeightBehavior GridView="{Binding ElementName=itemGridView}" FirstItem="{Binding ElementName=item}" SecondItem="{Binding ElementName=itemTitle}"/>
</Interactivity:Interaction.Behaviors>
Hope this helps.

Inherited Value in Column

we have a wpf-window with some textboxes and a datagrid.
the textboxes descripe a parent (class a) object and the datagrid lists a collection of "childs" (class b => not derived from class a).
the childs can inherit values from the parent.
for example if the parent (class a) has a property Foo then the child object (class b) has a property Nullable which can either override the value of the parent or inherit the value of the parent.
now the datagrid should display the value in gray (if it is inherited) or in black (if the user overrides the value in the grid cell).
Unfortunatly Binding to InheritedText doesnt work. Does someone have any idea?
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<UserControls:InheritedTextBoxControl
Text="{Binding Path=?}"
InheritedText="{Binding Path=?}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Thanks in advance
Tobi
--UPDATE--
xaml of InheritedTextBoxControl:
<UserControl x:Class="Com.QueoMedia.CO2Simulationstool.WPF.Utils.UserControls.InheritedTextBoxControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Width="Auto"
Height="Auto"
Name="cnt">
<Grid x:Name="LayoutRoot"
Background="White">
<TextBox TextChanged="TextBoxTextChanged"></TextBox>
<TextBlock Name="inheritedText"
IsHitTestVisible="False"
Margin="4,0"
VerticalAlignment="Center"
Opacity="0.5"
FontStyle="Italic"></TextBlock>
</Grid>
CodeBehind:
public partial class InheritedTextBoxControl : UserControl {
private bool _isInherited;
public static readonly DependencyProperty InheritedTextProperty = DependencyProperty.Register("InheritedText", typeof(String), typeof(InheritedTextBoxControl), new PropertyMetadata(""));
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(InheritedTextBoxControl), new PropertyMetadata(default(string)));
public InheritedTextBoxControl() {
InitializeComponent();
}
public string InheritedText {
get { return (string)GetValue(InheritedTextProperty); }
set {
SetValue(InheritedTextProperty, value);
inheritedText.Text = value;
}
}
private bool IsInherited {
get { return _isInherited; }
set {
_isInherited = value;
if (value) {
inheritedText.Opacity = 0.5;
} else {
inheritedText.Opacity = 0;
}
}
}
public string Text {
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
private void TextBoxTextChanged(object sender, TextChangedEventArgs e) {
if (((TextBox)sender).Text.Length > 0) {
IsInherited = false;
} else {
IsInherited = true;
}
Text = ((TextBox)sender).Text;
}
}
The problem is the setter of your InheritedText property. WPF won't call this setter when the property is set from XAML. See Checklist for Defining a Dependency Property, section Implementing the "Wrapper" for details.
You will have to update inheritedText.Text in a PropertyChangedCallback like this:
public static readonly DependencyProperty InheritedTextProperty =
DependencyProperty.Register(
"InheritedText", typeof(string), typeof(InheritedTextBoxControl),
new PropertyMetadata(string.Empty, InheritedTextChanged));
private static void InheritedTextChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((InheritedTextBoxControl)d).inheritedText.Text = (string)e.NewValue;
}
public string InheritedText
{
get { return (string)GetValue(InheritedTextProperty); }
set { SetValue(InheritedTextProperty, value); } // only call SetValue here
}
If someone is interested in the solution:
we did it using a CellTemplate containing a CustomControl name MaskedTextbox that has three properties (MaskedText, Text, IsMaskTextVisible) and a CellEditingTemplate to override the data.
The values are bound to an InheritableValueViewModel.
Tobi

Categories

Resources