INotifyDataErrorInfo (sometimes) does not work - c#

i wrote a control, derived from Textbox, where I can enter numbers in a special format.
To ensure, this format is correct I also implemented INotifyDataErrorInfo for validation.
However, after a few tests everything seems fine. The validaton pops up and also disappears again when the error has been fixed.
But now, I wanted to use the same control in another window and there it doesn't work anymore. The validation happens, the error is added to the dictionary and OnErrorsChanged is called, but after the ErrorHandler gets invoked neither the HasError property gets updated nor the GetErrors method is called and I cannot find out why this is the case.
In the other window, as said, everything works as expected.
Here is the important part of the control
public class UnitedStatesCustomaryUnitTextBox : TextBox, INotifyDataErrorInfo
{
static UnitedStatesCustomaryUnitTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(UnitedStatesCustomaryUnitTextBox), new FrameworkPropertyMetadata(typeof(UnitedStatesCustomaryUnitTextBox)));
}
private readonly Dictionary<string, List<string>> _propertyErrors = new Dictionary<string, List<string>>();
#region property Notifications
public static readonly DependencyProperty NotificationsProperty = DependencyProperty.Register(
"Notifications",
typeof(List<Notification>),
typeof(UnitedStatesCustomaryUnitTextBox),
new PropertyMetadata(default(List<Notification>), OnNotificationsChanged));
public List<Notification> Notifications
{
get
{
var result = (List<Notification>)GetValue(NotificationsProperty);
if (result != null)
return result;
result = new List<Notification>();
SetValue(NotificationsProperty, result);
return result;
}
set { SetValue(NotificationsProperty, value); }
}
private static void OnNotificationsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var ctl = sender as UnitedStatesCustomaryUnitTextBox;
if (ctl == null)
return;
var oldValue = e.OldValue as List<Notification>;
var newValue = e.NewValue as List<Notification>;
ctl.OnNotificationsChanged(oldValue, newValue);
}
private void OnNotificationsChanged(List<Notification> oldValue, List<Notification> newValue)
{
Client.Controls.UnitedStatesCustomaryUnitTextBox.UnitedStatesCustomaryUnitTextBox.OnNotificationsChanged
}
#endregion
#region property LengthUomId
public static readonly DependencyProperty LengthUomIdProperty = DependencyProperty.Register(
"LengthUomId",
typeof(int?),
typeof(UnitedStatesCustomaryUnitTextBox),
new PropertyMetadata(default(int?), OnLengthUomIdChanged));
public int? LengthUomId
{
get => (int?)GetValue(LengthUomIdProperty);
set => this.SetValue(LengthUomIdProperty, value);
}
#endregion
#region property Value
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value",
typeof(decimal?),
typeof(UnitedStatesCustomaryUnitTextBox),
new PropertyMetadata(default(decimal?), OnValueChanged));
public decimal? Value
{
get => (decimal?)GetValue(ValueProperty);
set => this.SetValue(ValueProperty, value);
}
private static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var ctl = sender as UnitedStatesCustomaryUnitTextBox;
if (ctl == null)
{
return;
}
var oldValue = e.OldValue as decimal?;
var newValue = e.NewValue as decimal?;
ctl.OnValueChanged(oldValue, newValue);
}
private void OnValueChanged(decimal? oldValue, decimal? newValue)
{
if (!this._isCalculating)
this.SetCurrentValue(TextProperty, this.CalculateFeetInchSixteenth(newValue));
}
#endregion
private bool IsFeetInchSixteenth => Defaults.UomDefaults.DefaultLengthUomId == this.LengthUomId;
protected override void OnTextChanged(TextChangedEventArgs e)
{
this._isCalculating = true;
if (!this.IsFeetInchSixteenth)
{
if (decimal.TryParse(this.Text, out decimal d))
this.Value = d;
return;
}
if (this.ValidateText(this.Text))
this.CalculateValue(this.Text);
this._isCalculating = false;
base.OnTextChanged(e);
}
private bool _isCalculating { get; set; }
private void CalculateValue(string text)
{
var numbers = text.Split('-');
this.Value = Convert.ToDecimal(
int.Parse(numbers[0]) * 192 +
(int.Parse(numbers[1]) * 16) +
(int.Parse(numbers[2]) * 1));
}
private string CalculateFeetInchSixteenth(decimal? value)
{
if (value == null)
return "0-0-0";
var feet = Math.Truncate(value.Value / 192);
var inch = Math.Truncate((value.Value - (feet * 192)) / 16);
var sixteenth = Math.Truncate(value.Value - (feet * 192) - (inch * 16));
return $"{feet}-{inch}-{sixteenth}";
}
private bool ValidateText(string text)
{
this._propertyErrors.Clear();
this.Notifications?.Clear();
this.OnErrorsChanged(nameof(this.Text));
if (string.IsNullOrWhiteSpace(text))
return false;
var numbers = text.Split('-');
if (numbers.Length != 3)
{
var notification = new Notification(
NotificationType.Error,
"FISC0001",
NotificationResources.FISC0001,
NotificationLocalizer.Localize(() => NotificationResources.FISC0001, new object[] { string.Empty }),
null);
this.AddError(nameof(this.Text), notification);
return false;
}
if (!this.CheckNumberRange(numbers))
{
var notification = new Notification(
NotificationType.Error,
"FISC0002",
NotificationResources.FISC0002,
NotificationLocalizer.Localize(() => NotificationResources.FISC0002, new object[] { string.Empty }),
null);
this.AddError(nameof(this.Text), notification);
return false;
}
return true;
}
private bool CheckNumberRange(string[] numbers)
{
if (!int.TryParse(numbers[0], out int number1))
return false;
if (!int.TryParse(numbers[1], out int number2))
return false;
if (!int.TryParse(numbers[2], out int number3))
return false;
return this.IsBetween(number2, 0, 11) && this.IsBetween(number3, 0, 15);
}
[DebuggerStepThrough]
private bool IsBetween(int number, int min, int max)
{
return number >= min && number <= max;
}
public IEnumerable GetErrors(string propertyName)
{
this._propertyErrors.TryGetValue(propertyName, out List<string> errors);
return errors;
}
public bool HasErrors => this._propertyErrors.Any();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void AddError(string propertyName, Notification notification)
{
if (!this._propertyErrors.ContainsKey(propertyName))
this._propertyErrors.Add(propertyName, new List<string>());
this._propertyErrors[propertyName].Add(notification.LocalizedMessage ?? notification.Message);
this.OnErrorsChanged(propertyName);
this.Notifications.Add(notification);
}
private void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
}
In the xaml I'm using the control like this
<unitedStatesCustomaryUnitTextBox:UnitedStatesCustomaryUnitTextBox
HorizontalAlignment="Stretch"
Visibility="{Binding IsLengthUnitedStatesCustomaryUnit.Value, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource FalseToCollapsedConverter}}"
Value="{Binding MeasuredLiquidLevelValue.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
LengthUomId="{Binding MeasuredLiquidLevelUom.Value.Id}"
Notifications="{Binding LengthErrors}"
Margin="{DynamicResource DefaultMarginAll}">
<i:Interaction.Behaviors>
<ncshared:LostFocusToCommandBehavior Command="{Binding CalculationRelatedControlLostFocusCommand}" />
</i:Interaction.Behaviors>
</unitedStatesCustomaryUnitTextBox:UnitedStatesCustomaryUnitTextBox>
I also tried setting the properties like ValidateOnDataErrors to true with no effect (they are true by default I think)

Thanks for the answers.
I implemented the changes you suggested regarding the DependencyProperty getters.
Also I found a solution for my problem.
Setting ValidateOnNotifyDataErrors resolved the problem with the GetErrors method not being called.
It also seems that I just forgot to set a style for the control (derived from Textbox), so the application didn't know how to render the validation error.
Although it shouldn't it works fine now

Related

Child properties update calling it's parent's `OnPropertyChanged`

I'm trying to create a XF component whose some properties are of a type that inherits from BindableObject. For illustrating, I have class Shadow with double Radius and Color ShadowColor properties and a class MyBoxText, that have a bool IsLoading and a Shadow Ghost properties.
My View and it's Bindings is working as expected, but I have an issue with it's custom renderer:
When I change the Ghost properties I need to redraw the entire view (of the MyBoxText control), to visually update the shadow color, for example.
Here's some mcve:
Classes code:
public class MyBoxText : Label /* It's bindable by inheritance */
{
#region Properties
public static readonly BindableProperty IsLoadingProperty = BindableProperty.Create(nameof(IsLoading), typeof(bool), typeof(MyBoxText), false) ;
public bool IsLoading
{
get { return (bool)GetValue(IsLoadingProperty); }
set { SetValue(IsLoadingProperty, value); }
}
public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null) ;
public Shadow Ghost
{
get { return (Shadow)GetValue(GhostProperty); }
set { SetValue(GhostProperty, value); }
}
#endregion
}
public class Shadow : BindableObject /* It's explictly bindable */
{
#region Properties
public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create(nameof(ShadowColor), typeof(Color), typeof(Shadow), Color.Black) ;
public Color ShadowColor
{
get { return (Color)GetValue(ShadowColorProperty); }
set { SetValue(ShadowColorProperty, value); }
}
public static readonly BindableProperty ShadowRadiusProperty = BindableProperty.Create(nameof(ShadowRadius), typeof(double), typeof(Shadow), 20) ;
public double ShadowRadius
{
get { return (double)GetValue(ShadowRadiusProperty); }
set { SetValue(ShadowRadiusProperty, value); }
}
#endregion
public Shadow()
{
}
}
My renderer's code is like this:
public class MyBoxText : LabelRenderer
{
public MyBoxText()
{
SetWillNotDraw(false);
}
public override void Draw(Canvas canvas)
{
MyBoxText myView = (MyBoxText)this.Element;
// Some drawing logic
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == MyBoxText.IsLoadingProperty.PropertyName ||
e.PropertyName == MyBoxText.GhostProperty.PropertyName )
Invalidate();
}
}
The issue is that when I change the Ghost.ShadowColor property my 'OnElementPropertyChanged' override is not called, and the View stays with the old color on the screen.
Is there a way to propagate the child's 'Property Update' event to parent view 'Property Changed' or another way to achieve this?
The issue is that when I change the Ghost.ShadowColor property my 'OnElementPropertyChanged' override is not called, and the View stays with the old color on the screen.
Is there a way to propagate the child's 'Property Update' event to parent view 'Property Changed' or another way to achieve this?
Yes, there is a way. Since your Shadow inherits from BindableObject, which implements the INotifyPropertyChanged Interface. You can set notify ShadowColor change:
Add OnPropertyChanged() to Setter of ShadowColor in Shadow.cs:
public class Shadow : BindableObject /* It's explictly bindable */
{
#region Properties
public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create(nameof(ShadowColor), typeof(Color), typeof(Shadow), Color.Black);
public Color ShadowColor
{
get { return (Color)GetValue(ShadowColorProperty); }
set { SetValue(ShadowColorProperty, value);
//Notify the ShadowColorProperty Changed
OnPropertyChanged();
}
}
...
}
Modify your MyBoxText.cs like this:
public class MyBoxText : Label /* It's bindable by inheritance */
{
...
public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null);
public Shadow Ghost
{
get { return (Shadow)GetValue(GhostProperty); }
set {
//register the ShadowColor change event
value.PropertyChanged += ShadowColor_PropertyChanged;
SetValue(GhostProperty, value); }
}
private void ShadowColor_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
//unregister the event
this.Ghost.PropertyChanged -= ShadowColor_PropertyChanged;
//set this.Ghost to a new object with new ShadowColor to trigger the OnPropertyChanged
this.Ghost = new Shadow
{
ShadowColor = (sender as Shadow).ShadowColor,
ShadowRadius = Ghost.ShadowRadius
};
}
}
Thanks to Elvis's answer I got it. Based on his idea I've made some changing to reuse it on other components and I'm sharing it now just in case someone else needs something like this.
I thought that use it this way we could get a cleaner and simple code:
public class MyBoxText : Label /* It's bindable by inheritance */
{
// Added this as private property
private ChangingPropagator changingPropagator;
private ChangingPropagator ChangingPropagator
{
get
{
if (changingPropagator == null)
changingPropagator = new ChangingPropagator(this, OnPropertyChanged, nameof(Shadow.ShadowColor), nameof(Shadow.ShadowRadius));
return changingPropagator;
}
}
#region Properties
public static readonly BindableProperty IsLoadingProperty = BindableProperty.Create(nameof(IsLoading), typeof(bool), typeof(MyBoxText), false) ;
public bool IsLoading
{
get { return (bool)GetValue(IsLoadingProperty); }
set { SetValue(IsLoadingProperty, value); }
}
public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null) ;
public Shadow Ghost
{
// Here I use the ChangingPropagator's Getter and Setter instead of the deafult ones:
get { return ChangingPropagator.GetValue<Shadow>(GhostProperty); }
set { ChangingPropagator.SetValue(GhostProperty,ref value); }
}
#endregion
}
And it's the ChangingPropagator class:
public class ChangingPropagator
{
string[] listenedProperties = new string[0];
Action<string> changesNotifyer = null;
BindableObject propagationRootObject = null;
List<KeyValuePair<string, object>> propagationProperties = new List<KeyValuePair<string, object>>();
public ChangingPropagator(BindableObject bindableObject, Action<string> onPropertyChangedMethod, params string[] propertyToListenTo)
{
changesNotifyer = onPropertyChangedMethod;
propagationRootObject = bindableObject;
listenedProperties = propertyToListenTo ?? listenedProperties;
// ToDo: Add some consistency checks
}
public void AddPropertyToListenTo(params string[] propertyName)
{
listenedProperties = listenedProperties.Union(propertyName).ToArray();
}
// I need handle it here too 'cause when I use the child `Ghost` property coming from XAML binding, it didn't hit the `set` method
public T GetValue<T>(BindableProperty property)
{
var value = propagationRootObject?.GetValue(property);
if (value != null)
{
INotifyPropertyChanged bindableSubObject = (value as INotifyPropertyChanged);
if (bindableSubObject != null)
{
bindableSubObject.PropertyChanged -= PropagatorListener;
bindableSubObject.PropertyChanged += PropagatorListener;
if (!propagationProperties.Any(a => a.Key == property.PropertyName))
propagationProperties.Add(new KeyValuePair<string, object>(property.PropertyName, value));
}
}
return (T)value;
}
public void SetValue<T>(BindableProperty property, ref T value)
{
var oldValue = propagationRootObject?.GetValue(property);
if (oldValue != null)
{
INotifyPropertyChanged bindableSubObject = (value as INotifyPropertyChanged);
if (bindableSubObject != null)
bindableSubObject.PropertyChanged -= PropagatorListener;
}
if (value != null)
{
INotifyPropertyChanged bindableSubObject = (value as INotifyPropertyChanged);
if (bindableSubObject != null)
{
bindableSubObject.PropertyChanged += PropagatorListener;
propagationProperties.RemoveAll(p => p.Key == property.PropertyName);
propagationProperties.Add(new KeyValuePair<string, object>(property.PropertyName, value));
}
}
propagationRootObject.SetValue(property, value);
}
private void PropagatorListener(object sender, PropertyChangedEventArgs e)
{
if (listenedProperties?.Contains(e.PropertyName) ?? true)
PropagationThrower(sender);
}
private void PropagationThrower(object sender)
{
if (propagationProperties.Any(p => p.Value == sender))
{
var prop = propagationProperties.FirstOrDefault(p => p.Value == sender);
changesNotifyer?.Invoke(prop.Key);
}
}
}

Static property not updating in UI

I spent the last hour(s) trying to find an answer in google and stackoverflow. I followed different advices & suggestions, but nothing worked so far. My current code looks like this:
public class GlobalManager : ViewModelBase
{
static object _LockObject_GFS = new object();
static double _GlobalFontSize;
public static double GlobalFontSize
{
get
{
lock (_LockObject_GFS)
{
_GlobalFontSize = GetGlobalResource<double>(LambdaHelper.MemberToString(() => GlobalFontSize));
return _GlobalFontSize;
}
}
set
{
lock (_LockObject_GFS)
{
if (_GlobalFontSize != value)
{
_GlobalFontSize = value;
SetGlobalResource(value, LambdaHelper.MemberToString(() => GlobalFontSize));
NotifyStaticPropertyChanged(() => GlobalFontSize);
}
}
}
}
}
The getter & setter are both called. NotifyStaticPropertyChanged works and my UI does not update. I've added a TextBlock to check if it updates. Apparently it does not.
<TextBlock Text="{Binding Path=(global:GlobalManager.GlobalFontSize), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
If I define a property in my VM (current DataContext), and bind it to a TextBlock, it updates correctly with the current value.
Currently the DependencyProperty Value of a Slider is bound to this property in order to update the font size. (IsSnapToTickEnabled="True")
public double GlobalFontSize
{
get { return GlobalManager.GlobalFontSize; }
set { GlobalManager.GlobalFontSize = value; NotifyPropertyChanged(() => GlobalFontSize); }
}
How do I get the binding to work correctly with the static property ? The StaticPropertyChanged event is not null.
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
Edit 1:
public static void NotifyStaticPropertyChanged(string propertyName)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
public static void NotifyStaticPropertyChanged<T>(Expression<Func<T> > property)
{
var expr = property.Body as MemberExpression;
if (expr == null)
throw new ArgumentException("Lambda does not contain member expression. () => MyClassOrObject.Property");
NotifyStaticPropertyChanged(expr.Member.Name);
}
Make sure that your GetGlobalResource and SetGlobalResource methods work as expected and that your event signature is correct.
You could refer to the below working sample implementation and compare it to yours:
public class GlobalManager
{
static object _LockObject_GFS = new object();
static double _GlobalFontSize;
public static double GlobalFontSize
{
get
{
lock (_LockObject_GFS)
{
return _GlobalFontSize;
}
}
set
{
lock (_LockObject_GFS)
{
if (_GlobalFontSize != value)
{
_GlobalFontSize = value;
NotifyStaticPropertyChanged(()=> GlobalFontSize);
}
}
}
}
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
public static void NotifyStaticPropertyChanged(string propertyName)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
public static void NotifyStaticPropertyChanged<T>(Expression<Func<T>> property)
{
var expr = property.Body as MemberExpression;
if (expr == null)
throw new ArgumentException("Lambda does not contain member expression. () => MyClassOrObject.Property");
NotifyStaticPropertyChanged(expr.Member.Name);
}
}
Edit: It doesn't work if the event is defined in a base class though.
public abstract class MyBaseViewModel
{
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
public static void NotifyStaticPropertyChanged(string propertyName)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
public static void NotifyStaticPropertyChanged<T>(Expression<Func<T>> property)
{
var expr = property.Body as MemberExpression;
if (expr == null)
throw new ArgumentException("Lambda does not contain member expression. () => MyClassOrObject.Property");
NotifyStaticPropertyChanged(expr.Member.Name);
}
}
public class GlobalManager : MyBaseViewModel
{
static object _LockObject_GFS = new object();
static double _GlobalFontSize = 10.0;
public static double GlobalFontSize
{
get
{
lock (_LockObject_GFS)
{
return _GlobalFontSize;
}
}
set
{
lock (_LockObject_GFS)
{
if (_GlobalFontSize != value)
{
_GlobalFontSize = value;
NotifyStaticPropertyChanged("GlobalFontSize");
}
}
}
}
}
The StaticPropertyChangedEvent must be defined in same class where property resides for the binding to get updated:
View is not getting notified when value of static Property Changes

Specified element is already the logical child of another element. Disconnect it first. in User Control

I have this UserControl:
[ContentProperty("Items")]
[DefaultProperty("Items")]
public partial class PanelControl : UserControl
{
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(PanelControl), new FrameworkPropertyMetadata(Orientation.Horizontal, new PropertyChangedCallback(OnOrientationChanged)));
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<UIElement>), typeof(PanelControl), new FrameworkPropertyMetadata(new ObservableCollection<UIElement>(), new PropertyChangedCallback(OnItemsChanged)));
public static readonly DependencyProperty SizeProperty = DependencyProperty.RegisterAttached("Size", typeof(double), typeof(PanelControl), new FrameworkPropertyMetadata(1.0, new PropertyChangedCallback(OnSizeChanged)), new ValidateValueCallback(IsSizeValid));
public Orientation Orientation
{
get
{
return (Orientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
public ObservableCollection<UIElement> Items
{
get
{
return (ObservableCollection<UIElement>)GetValue(ItemsProperty);
}
set
{
SetValue(ItemsProperty, value);
}
}
public static void SetSize(UIElement element, double size)
{
element.SetValue(SizeProperty, size);
}
public static double GetSize(UIElement element)
{
return (double)element.GetValue(SizeProperty);
}
private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
/*MessageBox.Show("orientation");*/
((PanelControl)dependencyObject).ClearAndBuildGrid();
}
private static void OnItemsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
/*MessageBox.Show("items");*/
((PanelControl)dependencyObject).ClearAndBuildGrid();
if(args.OldValue != null)
((ObservableCollection<UIElement>)args.OldValue).CollectionChanged -= ((PanelControl)dependencyObject).OnItemsCollectionChanged;
if (args.NewValue != null)
((ObservableCollection<UIElement>)args.NewValue).CollectionChanged += ((PanelControl)dependencyObject).OnItemsCollectionChanged;
}
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
/*MessageBox.Show("collection");*/
ClearAndBuildGrid();
}
private static void OnSizeChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
((PanelControl)dependencyObject).ClearAndBuildGrid();
/*MessageBox.Show("size");*/
}
private static bool IsSizeValid(object value)
{
return (double)value < 0 ? false : true;
}
private void ClearAndBuildGrid()
{
MainGrid.Children.Clear();
MainGrid.RowDefinitions.Clear();
MainGrid.ColumnDefinitions.Clear();
/*MessageBox.Show(MainGrid.Children.Count.ToString());*/
for (int i = 0; i < Items.Count; i++)
{
if (Orientation == Orientation.Horizontal)
{
MainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = SizeToGridLength(GetSize(Items[i])) });
Grid.SetColumn(Items[i], i * 2);
if (i != Items.Count - 1)
{
MainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(5) });
GridSplitter splitter = new GridSplitter() { ResizeDirection = GridResizeDirection.Columns, HorizontalAlignment = HorizontalAlignment.Stretch };
Grid.SetColumn(splitter, i * 2 + 1);
MainGrid.Children.Add(splitter);
}
}
else
{
MainGrid.RowDefinitions.Add(new RowDefinition() { Height = SizeToGridLength(GetSize(Items[i])) });
Grid.SetRow(Items[i], i * 2);
if (i != Items.Count - 1)
{
MainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(5) });
GridSplitter splitter = new GridSplitter() { ResizeDirection = GridResizeDirection.Rows, VerticalAlignment = VerticalAlignment.Stretch };
Grid.SetRow(splitter, i * 2 + 1);
MainGrid.Children.Add(splitter);
}
}
MainGrid.Children.Add(Items[i]);
}
}
private GridLength SizeToGridLength(double size)
{
return new GridLength(size, GridUnitType.Star);
}
public PanelControl()
{
InitializeComponent();
Items.CollectionChanged += OnItemsCollectionChanged;
}
}
And I use it here:
<p:PanelControl>
<Button />
<Button />
<Button />
</p:PanelControl>
When i start application it works good, but in designer I have underlined first button in xaml and error "Specified element is already the logical child of another element. Disconnect it first." Thanks for help, sorry for my bad English.
Not sure what is going on with the designer but this will 'fix' it.
Change the line:
MainGrid.Children.Add(Items[i]);
To:
var parent = VisualTreeHelper.GetParent(Items[i]) as Grid;
if (parent != null)
parent.Children.Remove(Items[i]);
MainGrid.Children.Add(Items[i]);
Using the code from J.H., I would move it up to the top of your function, so that the state of your grid when you clear it, is "cleared", and all the children have been disconnected from the Visual Tree.
private void ClearAndBuildGrid()
{
foreach (var item in Items)
{
var parent = System.Windows.Media.VisualTreeHelper.GetParent(item) as Grid;
if (parent != null)
parent.Children.Remove(item);
}
MainGrid.Children.Clear();
It would be a style/intention/preference thing, and to be clear, the answer J.H. gives is totally valid.
Consider using foreach instead of for and not having to deal with array subscripts.

How to set the margin on a internal TextBoxView in wpf

I have a case where I want to minimize the horizontal padding of a textbox.
Using snoop I found that the textbox consists of a multiple sub-controls.
One of them is a TextBoxView with a margin of 2,0,2,0
The TextBoxView is an internal wpf component and has no public API.
How would you approach getting rid of the "internal padding"??
Set the outer margin to -2,0,-2,0 to compensate for the padding.
I created a custom control that removes that internal padding.
public class MyTextBox : TextBox
{
public MyTextBox()
{
Loaded += OnLoaded;
}
void OnLoaded(object sender, RoutedEventArgs e)
{
// the internal TextBoxView has a margin of 2,0,2,0 that needs to be removed
var contentHost = Template.FindName("PART_ContentHost", this) as ScrollViewer;
if (contentHost != null && contentHost.Content != null && contentHost.Content is FrameworkElement)
{
var textBoxView = contentHost.Content as FrameworkElement;
textBoxView.Margin = new Thickness(0,0,0,0);
}
}
}
Here is a dirty way of doing it:
public static class TextBoxView
{
public static readonly DependencyProperty MarginProperty = DependencyProperty.RegisterAttached(
"Margin",
typeof(Thickness?),
typeof(TextBoxView),
new PropertyMetadata(null, OnTextBoxViewMarginChanged));
public static void SetMargin(TextBox element, Thickness? value)
{
element.SetValue(MarginProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static Thickness? GetMargin(TextBox element)
{
return (Thickness?)element.GetValue(MarginProperty);
}
private static void OnTextBoxViewMarginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = (TextBox)d;
OnTextBoxViewMarginChanged(textBox, (Thickness?)e.NewValue);
}
private static void OnTextBoxViewMarginChanged(TextBox textBox, Thickness? margin)
{
if (!textBox.IsLoaded)
{
textBox.Dispatcher.BeginInvoke(
DispatcherPriority.Loaded,
new Action(() => OnTextBoxViewMarginChanged(textBox, margin)));
return;
}
var textBoxView = textBox.NestedChildren()
.SingleOrDefault(x => x.GetType().Name == "TextBoxView");
if (margin == null)
{
textBoxView?.ClearValue(FrameworkElement.MarginProperty);
}
else
{
textBoxView?.SetValue(FrameworkElement.MarginProperty, margin);
}
}
private static IEnumerable<DependencyObject> NestedChildren(this DependencyObject parent)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
yield return child;
if (VisualTreeHelper.GetChildrenCount(child) == 0)
{
continue;
}
foreach (var nestedChild in NestedChildren(child))
{
yield return nestedChild;
}
}
}
}
It allows setting the margin on textboxes:
<Style TargetType="{x:Type TextBox}">
<Setter Property="demo:TextBoxView.Margin" Value="1,0" />
</Style>
Not optimized for performance at all.

Binding two Dependency properties in WPF

I declare two Dependency properties:
first, FilterColor of type Color
and second FilterBrush of type Brush.
I need to update value of FilterColor when FilterBrush.Color property has changed, and I need to update value of FilterBrush.Color when FilterColor property has changed.
How I can realize it?
Bind your two properties with TwoWay binding and if you change one in the UI change the other in the properties setter and Vice Versa, and use INotifyPropertyChanged to Notify your UI that the property changed.
You can either do it in the DependencyProperty definition, or you can use a DependencyPropertyDescriptor to do it afterwards
For example....
DependencyProperty Definition:
public static readonly DependencyProperty FilterColorProperty =
DependencyProperty.Register("FilterColor", typeof(Color),
typeof(MyUserControl),
new PropertyMetadata(Colors.White, FilterColorChanged));
public static void FilterColorChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is MyUserControl))
return;
MyUserControl ctrl = (MyUserControl)obj;
var brush = ctrl.GetBrushProperty();
if (brush.Color != (Color)e.NewValue)
brush.Color = (Color)e.NewValue;
}
DependencyProperty Descriptor:
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(
MyUserControl.FilterColorProperty, typeof(MyUserControl));
if (dpd != null)
dpd.AddValueChanged(this, delegate { FilterColor_Changed(); });
...
private void FilterColor_Changed()
{
Color filterColor = GetFilterColor(this);
var brush = GetBrush(this);
if (filterColor != brush.Color)
brush.Color = filterColor;
}
I may have a few syntax errors... I don't have a compiler to check the code
Where did you define these properties: in view model or in control?
If it's in view model you should use INotifyPropertyChanged instead of dependency properties like this:
Color _filterColor;
public Color FilterColor
{
get
{
return _filterColor;
}
{
if (_filterColor != value)
{
_filterColor = value;
RaisePropertyChanged(() => FilterColor);
_OnFilterColorChanged();
}
}
void _OnFilterColorChanged()
{
_filterBrush= ...
RaisePropertyChanged(() => FilterBrush);
}
Brush _filterBrush;
public Brush FilterBrush
{
get
{
return _filterBrush;
}
{
if (_filterBrush != value)
{
_filterBrush = value;
RaisePropertyChanged(() => FilterBrush);
_OnFilterBrushChanged();
}
}
void _OnFilterBrushChanged()
{
_filterColor= ...
RaisePropertyChanged(() =. FilterColor);
}
If it is in control do this:
public Color FilterColor
{
get { return (Color)GetValue(FilterColorProperty); }
set { SetValue(FilterColorProperty, value); }
}
public static readonly DependencyProperty FilterColorProperty =
DependencyProperty.Register("FilterColor", typeof(Color), typeof(MainWindow), new UIPropertyMetadata(Colors.Transparent, new PropertyChangedCallback(_OnFilterColorPropertyChanged)));
static void _OnFilterColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var mw = d as MainWindow;
Color oldValue = (Color)e.OldValue;
Color newValue = (Color)e.NewValue;
if (null != mw && oldValue != newValue)
{
mw._OnFilterColorChanged(oldValue, newValue);
}
}
bool _isFilterColorUpdating = false;
void _OnFilterColorChanged(Color oldValue, Color newValue)
{
if (_isFilterBrushUpdating )
return;
_isFilterColorUpdating = true;
Brush = ...
_isFilterColorUpdating = false;
}
public Brush FilterBrush
{
get { return (Brush)GetValue(FilterBrushProperty); }
set { SetValue(FilterBrushProperty, value); }
}
public static readonly DependencyProperty FilterBrushProperty =
DependencyProperty.Register("FilterBrush", typeof(Brush), typeof(MainWindow), new UIPropertyMetadata(Brushs.Transparent, new PropertyChangedCallback(_OnFilterBrushPropertyChanged)));
static void _OnFilterBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var mw = d as MainWindow;
Brush oldValue = (Brush)e.OldValue;
Brush newValue = (Brush)e.NewValue;
if (null != mw && oldValue != newValue)
{
mw._OnFilterBrushChanged(oldValue, newValue);
}
}
bool _isFilterBrushUpdating = false;
void _OnFilterBrushChanged(Brush oldValue, Brush newValue)
{
if (_isFilterColorUpdating )
return;
_isFilterBrushUpdating = true;
Color = ...
_isFilterBrushUpdating = false;
}
Note that last way is just hack and it is really bad way, I would prefer the first way.

Categories

Resources