I can successfully draw part of a custom control in the control's OnApplyTemplate
method, however, this method is called only once and does not appear to be called when I called ApplyTemplate
when the control's Num
property changes.
Here is the custom control's code:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Cc1
{
public class CirclesControl : Control
{
static CirclesControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CirclesControl), new FrameworkPropertyMetadata(typeof(CirclesControl)));
}
public int Num
{
get { return (int)GetValue(NumProperty); }
set { SetValue(NumProperty, value); }
}
public static readonly DependencyProperty NumProperty =
DependencyProperty.Register("Num", typeof(int), typeof(CirclesControl), new PropertyMetadata(2, new PropertyChangedCallback(OnNumChanged)));
private static void OnNumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CirclesControl control)
{
System.Diagnostics.Debug.WriteLine($"Num changed - value is now {control.Num}");
control.ApplyTemplate();
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var canvasPart = Template.FindName("Part_Canvas", this);
if (canvasPart is Canvas canvas)
{
for (int i = 0; i < Num; i++)
{
var circle = new Ellipse
{
Height = 50,
Width = 50,
Fill = Brushes.Red,
Stroke = Brushes.Green
};
canvas.Children.Add(circle);
Canvas.SetLeft(circle, i * 55);
Canvas.SetTop(circle, 50);
}
}
}
}
}
I know that OnNumChanged
is called because System.Diagnostics.Debug.WriteLine
writes a message to the output window. OnNumChanged
also calls ApplyTemplate
but this does not cause OnApplyTemplate
to be called.
Here is the main window's xaml:
<Window x:Class="Cc1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Cc1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="1024">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<StackPanel>
<TextBlock Text="{Binding NumberOfCircles}" FontSize="36"/>
<local:CirclesControl Num="{Binding NumberOfCircles}"/>
</StackPanel>
</Window>
The generic.xaml
for the control:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Cc1">
<Style TargetType="{x:Type local:CirclesControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CirclesControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Canvas x:Name="Part_Canvas"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And the viewmodel:
using System;
using System.Windows;
using System.Windows.Threading;
namespace Cc1
{
public class ViewModel : DependencyObject
{
public ViewModel()
{
var timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(500)
};
timer.Tick += (s, e) =>
{
NumberOfCircles++;
if (NumberOfCircles == 10)
{
NumberOfCircles = 2;
}
};
timer.Start();
}
public int NumberOfCircles
{
get { return (int)GetValue(NumberOfCirclesProperty); }
set { SetValue(NumberOfCirclesProperty, value); }
}
public static readonly DependencyProperty NumberOfCirclesProperty =
DependencyProperty.Register("NumberOfCircles", typeof(int), typeof(ViewModel), new PropertyMetadata(4));
}
}
The number of circles (in the view model) changes all the time (this is what the dispatcher timer does). The TextBlock
on the main windows accurately reflects the number of circles however the CirclesControl
custom control on the main window does not update.
Grateful for any hints as to what is missing.