Why does the custom panel not refreshing automatically - c#

I have written a custom panel. Here is the code:
public class GameBoardPanel:Panel
{
public GameBoardPanel() { }
public static readonly DependencyProperty SquareSideLengthProperty =
DependencyProperty.Register("SquareSideLength", typeof(int), typeof(GameBoardPanel), new PropertyMetadata(0));
public int SquareSideLength
{
get { return (int)GetValue(SquareSideLengthProperty); }
private set { SetValue(SquareSideLengthProperty, value); }
}
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register("Rows", typeof(int), typeof(GameBoardPanel), new PropertyMetadata(0));
public int Rows
{
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(int), typeof(GameBoardPanel), new PropertyMetadata(0));
public static readonly DependencyProperty RowProperty =
DependencyProperty.RegisterAttached("Row", typeof(int), typeof(GameBoardPanel));
public static readonly DependencyProperty ColumnProperty =
DependencyProperty.RegisterAttached("Column", typeof(int), typeof(GameBoardPanel));
public static void SetRow(DependencyObject control, int value)
{
control.SetValue(RowProperty, value);
}
public static int GetRow(DependencyObject control)
{
return (int)control.GetValue(RowProperty);
}
public static void SetColumn(DependencyObject control, int value)
{
control.SetValue(ColumnProperty, value);
}
public static int GetColumn(DependencyObject control)
{
return (int)control.GetValue(ColumnProperty);
}
protected override Size MeasureOverride(Size availableSize)
{
double min=Math.Min(availableSize.Width/Columns,availableSize.Height/Rows );
SquareSideLength=(int)min;
return new Size(SquareSideLength*Columns, SquareSideLength*Rows);
}
protected override Size ArrangeOverride(Size finalSize)
{
double min=Math.Min(finalSize.Width/Columns,finalSize.Height/Rows );
SquareSideLength=(int)min;
Size destinationSize = new Size(SquareSideLength * Columns, SquareSideLength * Rows);
foreach (UIElement element in base.InternalChildren)
{
int row = GameBoardPanel.GetRow(element);
int column = GameBoardPanel.GetColumn(element);
element.Arrange(new Rect(column * SquareSideLength, row * SquareSideLength,SquareSideLength, SquareSideLength));
}
return destinationSize;
}
}
It is very simple idea, similar to wpf Grid panel. I have a control which arrange its children on such a panel by setting children GameBoardPanel.Row and GameBoardPanel.Column attached properties. Let's assume that I have also a GameObject class which is a CustomControl and I added it to the panel. I want to move it for example to the left. So I do it in the following way:
GameObject movable=new GameObject();
//setting properties such as GameBoardPanel.Row and GameBoardPanel.Column
panel.Children.Add(movable);
GameBoardPanel.SetColumn(GameBoardPanel.GetColumn(movable)-1);
Let's skip the problem that the movable object can move outside the panel. It's not a big problem. However if I did the movement in that way I didn't see any effect. Only after adding an additional line:
panel.InvalidateArrange();
I observed that the object moved left. I'm quite sure, that if I would do the same with the wpf Grid, it's not necessary to call InvalidateArrange method. So, I am curious why does this happen? :D

You may set appropriate property metadata options to tell the framework that the Row and Column attached properties affect the layout of your custom panel:
public static readonly DependencyProperty RowProperty =
DependencyProperty.RegisterAttached("Row", typeof(int), typeof(GameBoardPanel),
new FrameworkPropertyMetadata(0,
FrameworkPropertyMetadataOptions.AffectsParentArrange));
public static readonly DependencyProperty ColumnProperty =
DependencyProperty.RegisterAttached("Column", typeof(int), typeof(GameBoardPanel),
new FrameworkPropertyMetadata(0,
FrameworkPropertyMetadataOptions.AffectsParentArrange));
That said, you should usually also measure the child elements in the MeasureOverride method:
protected override Size MeasureOverride(Size availableSize)
{
double min = Math.Min(availableSize.Width / Columns, availableSize.Height / Rows);
SquareSideLength = (int)min;
foreach (UIElement element in InternalChildren)
{
element.Measure(new Size(SquareSideLength, SquareSideLength));
}
return new Size(SquareSideLength * Columns, SquareSideLength * Rows);
}
Moreover it also doesn't seem to be necessary that SquareSideLength is a dependency property. It could as well be an ordinary CLR property (or probably just a private field). Its type may also better be double instead of int.
Finally, your MeasureOverride implementation should also be prepared for an infinite availableSize parameter value. From MSDN:
The available size that this
element can give to child elements. Infinity can be specified as a
value to indicate that the element will size to whatever content is
available.

Related

Change attached property from CustomPanel doesn't refresh panel

I created dummy custom panel ShelfPanel with attached property Exact influence panel arrange:
class ShelfPanel : Panel
{
#region Start attached property
public static DependencyProperty ExactProperty = DependencyProperty.RegisterAttached("Exact", typeof(int), typeof(ShelfPanel),
new FrameworkPropertyMetadata(0,
FrameworkPropertyMetadataOptions.AffectsArrange | // Does this options have auto action?
FrameworkPropertyMetadataOptions.AffectsRender)); // Does this options have auto action?
public static void SetExact(UIElement element, int value)
{
element.SetValue(ExactProperty, value);
}
public static int GetExact(UIElement element)
{
return (int)element.GetValue(ExactProperty);
}
#endregion
protected override Size MeasureOverride(Size availableSize)
{
Size final = new Size();
foreach (UIElement child in InternalChildren)
{
child.Measure(availableSize);
final.Height += child.DesiredSize.Height;
}
return final;
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement child in InternalChildren)
{
Point position = new Point();
int exact = ShelfPanel.GetExact(child);
// Calculate position based on attached Exact
position.X = exact * 100;
position.Y = exact * 100;
child.Arrange(new Rect(position, child.DesiredSize));
}
return finalSize;
}
}
<local:ShelfPanel>
<local:Box local:ShelfPanel.Exact="0" MouseDown="Box_MouseDown"/>
<local:Box local:ShelfPanel.Exact="1" />
<local:Box local:ShelfPanel.Exact="2" />
</local:ShelfPanel>
public partial class MainWindow : Window // Codebehind for previous xaml
{
public MainWindow()
{
InitializeComponent();
}
private void Box_MouseDown(object sender, MouseButtonEventArgs e)
{
// I certainly sure this code get triggered after click.
ShelfPanel.SetExact(sender as UIElement, 3);
}
}
This works perfect <local:Box> are arranged as planned.
As you can deduce from code, after click on first <local:Box> it should change it position to 3, just after others 2. But surprisingly nothing happen.
Doesn't FrameworkPropertyMetadataOptions.Affects Arrange or FrameworkPropertyMetadataOptions.AffectsRender automatically repaint panel?
To make this works I need to add PropertyChangedCallbackand call there InvalidateVisual()?
You are setting the attached property on a Box, but want the parent element of the Box, i.e. the ShelfPanel to be arranged.
You should therefore set FrameworkPropertyMetadataOptions.AffectsParentArrange:
public static readonly DependencyProperty ExactProperty =
DependencyProperty.RegisterAttached("Exact", typeof(int), typeof(ShelfPanel),
new FrameworkPropertyMetadata(0,
FrameworkPropertyMetadataOptions.AffectsParentArrange));

Using attached properties in WPF

I am trying to create a Read Only attach property in WPF that will calculate the total Visual Child count of the control. The benefit of this is not important to me, it's being able to use attach properties properly!
Firstly I've declared my property like so:
internal static readonly DependencyPropertyKey TotalChildCountPropertyKey =
DependencyProperty.RegisterAttachedReadOnly("TotalChildCount", typeof(int), typeof(MyAttachClass), new FrameworkPropertyMetadata(0));
public static readonly DependencyProperty TotalChildCountProperty = TotalChildCountPropertyKey.DependencyProperty;
public static int GetTotalChildCount(DependencyObject obj)
{
return (int)obj.GetValue(TotalChildCountProperty);
}
public static void SetTotalChildCount(DependencyObject obj, int value)
{
obj.SetValue(TotalChildCountPropertyKey, value);
}
I also have a recursive method declared else where like so:
public static class Recursive
{
public static IEnumerable<DependencyObject> GetAllChildren(DependencyObject obj)
{
List<DependencyObject> col = new List<DependencyObject>();
GetAllChildrenImp(obj, col);
return col;
}
private static void GetAllChildrenImp(DependencyObject current, List<DependencyObject> col)
{
if (current != null)
{
col.Add(current);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++ )
{
GetAllChildrenImp(VisualTreeHelper.GetChild(current, i), col);
}
}
}
}
Now I wish to use this method to assign a value to the GetTotalChildCount property, but I cannot figure out the best way of doing. I could add an event handler to the dependency property changes, but this will never fire because I will only be reading from the value in xaml.
Here's how I am using it in xaml:
<TextBox DataContext="{RelativeSource Self}" Text="{Binding local:MyAttachClass.TotalChildCount}"></TextBox>
So to summarize. I wish to set a DependencyObjects TotalChildCount attach property and then be able to bind to it in xaml. As it stands this is not working, the GetTotalChildCount is not even getting hit.
Oh this is my first question, hopefully I was clear enough
You can try it this way
Attached property
public class MyAttachClass
{
public static readonly DependencyProperty TotalChildCountProperty = DependencyProperty.RegisterAttached("TotalChildCount", typeof(int), typeof(MyAttachClass),
new PropertyMetadata(-1, OnTotalChildCountChanged));
public static int GetTotalChildCount(DependencyObject obj)
{
return (int)obj.GetValue(TotalChildCountProperty);
}
public static void SetTotalChildCount(DependencyObject obj, int value)
{
obj.SetValue(TotalChildCountProperty, value);
}
public static void OnTotalChildCountChanged(object sender, DependencyPropertyChangedEventArgs e)
{
TextBox txt = sender as TextBox;
if (txt != null)
{
var children = Recursive.GetAllChildren(txt);
txt.Text = children.Count().ToString();
}
}
}
And the xaml as
<TextBox local:MyAttachClass.TotalChildCount="0" ></TextBox>
Hope it helps!

Using Dependency Properties With Multiple Instances

I basically have the same problem as this guy here: Error: " 'Subjects' property was already registered by 'Period' " is raised when more than one control is placed on the form
The main difference between us is that I want to subscribe to an event that has access to the local instance when the xaml changes the property.
So I have my UserControl:
public partial class BoardSquare : UserControl
{
public BoardSquare()
{
InitializeComponent();
Location = new BoardLocation(Int32.MinValue, Int32.MinValue);
XPositionProperty =
DependencyProperty.Register("XPosition", typeof(int),
typeof(BoardSquare), new PropertyMetadata(
new PropertyChangedCallback((value, args) =>
{
Location.X = (int)args.NewValue;
resetBackgroundColorToPosition();
})));
YPositionProperty=
DependencyProperty.Register("YPosition", typeof(int),
typeof(BoardSquare), new PropertyMetadata(
new PropertyChangedCallback((value, args)=>
{
Location.Y = (int)args.NewValue;
resetBackgroundColorToPosition();
})));
}
private void resetBackgroundColorToPosition()
{
this.Background = (Brush)(new ColorEnumToBrushesConverter()).Convert(Location.GetSquareColor(), typeof(BlackWhiteColor), null, null);
}
public readonly DependencyProperty XPositionProperty;
public readonly DependencyProperty YPositionProperty;
public int XPosition
{
get
{
return (int)GetValue(XPositionProperty);
}
set
{
SetValue(XPositionProperty, value);
}
}
public int YPosition
{
get
{
return (int)GetValue(YPositionProperty);
}
set
{
SetValue(YPositionProperty, value);
}
}
public BoardLocation Location { get; set; }
}
Here is my XAML:
<local:BoardSquare Grid.Column="3" Grid.Row="0" XPosition="3" YPosition="0"/>
<local:BoardSquare Grid.Column="4" Grid.Row="0" XPosition="4" YPosition="0"/>
From what I understand, the solution is to make XPositionProperty static and then register it in a static constructor. My problem then is I can't access the local instance of the class when my PropertyChangeCallback event happens.
How can I set the property in the XAML and still get an on property changed event in the C# code?
Is there a better solution than dependency properties?
Below is the working code of BoardSquare after I implemented the answer.
public partial class BoardSquare : UserControl
{
static BoardSquare()
{
XPositionProperty =
DependencyProperty.Register("XPosition", typeof(int),
typeof(BoardSquare), new PropertyMetadata(
new PropertyChangedCallback((objectInstance, args) =>
{
BoardSquare boardSquare = (BoardSquare)objectInstance;
boardSquare.Location.X = (int)args.NewValue;
boardSquare.resetBackgroundColorToPosition();
})));
YPositionProperty =
DependencyProperty.Register("YPosition", typeof(int),
typeof(BoardSquare), new PropertyMetadata(
new PropertyChangedCallback((objectInstance, args) =>
{
BoardSquare boardSquare = (BoardSquare)objectInstance;
boardSquare.Location.Y = (int)args.NewValue;
boardSquare.resetBackgroundColorToPosition();
})));
}
public BoardSquare()
{
InitializeComponent();
Location = new BoardLocation(Int32.MinValue, Int32.MinValue);
}
private void resetBackgroundColorToPosition()
{
this.Background = (Brush)(new ColorEnumToBrushesConverter()).Convert(Location.GetSquareColor(), typeof(BlackWhiteColor), null, null);
}
public static readonly DependencyProperty XPositionProperty;
public static readonly DependencyProperty YPositionProperty;
public int XPosition
{
get
{
return (int)GetValue(XPositionProperty);
}
set
{
SetValue(XPositionProperty, value);
}
}
public int YPosition
{
get
{
return (int)GetValue(YPositionProperty);
}
set
{
SetValue(YPositionProperty, value);
}
}
public BoardLocation Location { get; set; }
}
The first argument of PropertyChangedCallback is the local instance (btw. you better name it obj than value to avoid confusion). You have to cast this DependencyObject to BoardSquare and that's all.
public static readonly DependencyProperty XPositionProperty =
DependencyProperty.Register("XPosition", typeof(int), typeof(BoardSquare),
new PropertyMetadata(new PropertyChangedCallback((obj, args) => {
BoardSquare bs = obj as BoardSquare;
bs.Location.X = (int)args.NewValue;
bs.resetBackgroundColorToPosition();
})));
If I understand your question correctly, you are trying to override already WPF certificate property with your own. I don't think that's necessary here. If XPosition is already WPF certificate property, all you have to do is create a property in your usercontrol with getter/setter with setter calling INotifypropertychanged. you don't need dependency property in this case unless you want only same name but behave differently, and why do you want that?.

Storyboard doesn't animate custom FrameworkElement property

I have class that is derived from FrameworkElement, and I want WPF to update its Location property by using DoubleAnimation. I register the property as DependendencyProperty:
public class TimeCursor : FrameworkElement
{
public static readonly DependencyProperty LocationProperty;
public double Location
{
get { return (double)GetValue(LocationProperty); }
set
{
SetValue(LocationProperty, value);
}
}
static TimeCursor()
{
LocationProperty = DependencyProperty.Register("Location", typeof(double), typeof(TimeCursor));
}
}
Following code sets up the storyboard.
TimeCursor timeCursor;
private void SetCursorAnimation()
{
timeCursor = new TimeCursor();
NameScope.SetNameScope(this, new NameScope());
RegisterName("TimeCursor", timeCursor);
storyboard.Children.Clear();
DoubleAnimation animation = new DoubleAnimation(LeftOffset, LeftOffset + (VerticalLineCount - 1) * HorizontalGap + VerticalLineThickness,
new Duration(TimeSpan.FromMilliseconds(musicDuration)), FillBehavior.HoldEnd);
Storyboard.SetTargetName(animation, "TimeCursor");
Storyboard.SetTargetProperty(animation, new PropertyPath(TimeCursor.LocationProperty));
storyboard.Children.Add(animation);
}
Then I call storyboard.Begin(this) from another method of the object which contains the above SetCursorAnimation() method and this object is derived from Canvas. However the Location property is never updated(set accessor of Location is never called) and no exception is thrown. What am I doing wrong?
When a dependency property is animated (or set in XAML, or set by a Style Setter, etc.), WPF does not call the CLR wrapper, but instead directly accesses the underlying DependencyObject and DependencyProperty objects. See the Implementing the "Wrapper" section in Checklist for Defining a Dependency Property and also Implications for Custom Dependency Properties.
In order to get notified about property changes, you have to register a PropertyChangedCallback:
public class TimeCursor : FrameworkElement
{
public static readonly DependencyProperty LocationProperty =
DependencyProperty.Register(
"Location", typeof(double), typeof(TimeCursor),
new FrameworkPropertyMetadata(LocationPropertyChanged)); // register callback here
public double Location
{
get { return (double)GetValue(LocationProperty); }
set { SetValue(LocationProperty, value); }
}
private static void LocationPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var timeCursor = obj as TimeCursor;
// handle Location property changes here
...
}
}
And note also that animating a dependency property does not necessarily require a Storyboard. You could simply call the BeginAnimation method on your TimeCursor instance:
var animation = new DoubleAnimation(LeftOffset,
LeftOffset + (VerticalLineCount - 1) * HorizontalGap + VerticalLineThickness,
new Duration(TimeSpan.FromMilliseconds(musicDuration)),
FillBehavior.HoldEnd);
timeCursor.BeginAnimation(TimeCursor.LocationProperty, animation);

Shared dependency property update issue in binding

I have a read-only dependency property which is shared between two WPF classes. So I made this property shared property. While binding this property on XAML, It takes its default value and can't be updated. But If I use the main owner of this property, its value can be updated.
So what is your advice?
public class A
{
private static readonly DependencyPropertyKey XPropertyKey =
DependencyProperty.RegisterReadOnly("X",
typeof(Point), typeof(A),
new FrameworkPropertyMetadata(new Point(0, 0),
FrameworkPropertyMetadataOptions.Inherits |
FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty XProperty =
RelativeMousePointPropertyKey.DependencyProperty;
public Point X
{
get { return (Point)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
public void SetRelativeMousePoint(Point point)
{
SetValue(XPropertyKey, point);
}
}
public class B
{
//I use this class for binding
public static readonly DependencyProperty XProperty =
A.XProperty.AddOwner(typeof(B));
public Point X
{
get { return (Point)GetValue(X); }
}
}

Categories