Question

I've got a Custom Control defined as below, the Generic.xaml file only contains a template with a canvas called 'cvRoot' in it.

I cannot get the control to rerender itself properly when a one of the dependency properties changes. Whilst it may draw when I use it in an application, when I try and measure it, I always get a zero size back. This is incredibly frustrating as I need to get the size to align the control properly.

I've tried using FrameworkPropertyMetadata in the Dependency Property register method with the option set to AffectsRender, but this doesn't change what happens. I've also tried InvalidateVisual in the Callback for each property with no joy.

I'm really pulling my hair out here, any ideas?

namespace   GraphControls
{
    public class Marker :   Control
    {
        private Canvas cvRoot   =   null;
        private static Action   EmptyDelegate   =   delegate() { };

        static Marker()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Marker), new FrameworkPropertyMetadata(typeof(Marker)));
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            cvRoot = GetTemplateChild("cvRoot") as Canvas;
        }



        protected   override void   OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            cvRoot.Children.Clear();

            // Text
            //
            TextBlock   tb = new TextBlock();
            tb.Text =   Text;
            tb.FontSize =   MarkerFontSize;
            tb.Foreground   =   TextColour;
            tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));

            // Background   Ellipse
            //
            Ellipse el = new Ellipse();
            el.Width = tb.DesiredSize.Height;
            el.Height   =   tb.DesiredSize.Height;
            el.Fill =   MarkerFill;

            // Add to   Canvas and reposition
            //
            cvRoot.Children.Add(el);
            cvRoot.Children.Add(tb);
            Canvas.SetLeft(tb, (el.Width / 2)   -   (tb.DesiredSize.Width   /   2));
            Canvas.SetTop(tb,   (el.Height / 2) -   (tb.DesiredSize.Height / 2));

            // Resize   the owning Canvas   so that external Measures   work
            //
            cvRoot.Width = el.Width;
            cvRoot.Height   =   el.Height;

        }

        public static   DependencyProperty TextProperty =   DependencyProperty.Register("Text", typeof(string), typeof(Marker),
                                        new PropertyMetadata("X",   new PropertyChangedCallback(OnGenericDependencyPropertyChanged)));
        public static   DependencyProperty MarkerFontSizeProperty   =   DependencyProperty.Register("MarkerFontSize",   typeof(double), typeof(Marker),
                                        new PropertyMetadata(12D,   new PropertyChangedCallback(OnGenericDependencyPropertyChanged)));
        public static   DependencyProperty MarkerFillProperty   =   DependencyProperty.Register("MarkerFill",   typeof(Brush), typeof(Marker),
                                        new PropertyMetadata(new SolidColorBrush(Colors.Black), new PropertyChangedCallback(OnGenericDependencyPropertyChanged)));
        public static   DependencyProperty TextColourProperty   =   DependencyProperty.Register("TextColour",   typeof(Brush), typeof(Marker),
                                        new PropertyMetadata(new SolidColorBrush(Colors.White), new PropertyChangedCallback(OnGenericDependencyPropertyChanged)));

        public string   Text
        {
            get {   return (string)GetValue(TextProperty); }
            set {   SetValue(TextProperty, value); }
        }

        public double   MarkerFontSize
        {
            get {   return (double)GetValue(MarkerFontSizeProperty); }
            set {   SetValue(MarkerFontSizeProperty, value); }
        }

        public Brush MarkerFill
        {
            get {   return (Brush)GetValue(MarkerFillProperty); }
            set {   SetValue(MarkerFillProperty, value); }
        }

        public Brush TextColour
        {
            get {   return (Brush)GetValue(TextColourProperty); }
            set {   SetValue(TextColourProperty, value); }
        }

        private static void OnGenericDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs   e)
        {
            ((Marker)d).Dispatcher.Invoke(DispatcherPriority.Render,    EmptyDelegate);
        }

        protected   override Size   MeasureOverride(Size constraint)
        {
            Size retval =   new Size();

            // Handle   the template not being called   yet
            //
            if (cvRoot !=   null)
            {
                cvRoot.Measure(constraint);
                retval = cvRoot.DesiredSize;
            }

            return retval;

        }

    }
}
Était-ce utile?

La solution

From your code example, it appears that you are using the wrong kind of control to extend. From the Control Authoring Overview page on MSDN:

Benefits of Deriving from Control

Consider deriving from Control instead of using the UserControl class if any of the following apply:

•You want the appearance of your control to be customizable via the ControlTemplate.

•You want your control to support different themes.

Benefits of Deriving from UserControl

Consider deriving from UserControl if all of the following apply:

•You want to build your control similarly to how you build an application.

•Your control consists only of existing components.

•You don't need to support complex customization.

Therefore, I believe that you will have much more luck if you use a UserControl and declare your UI elements in XAML. However, if you are sure that you want to use a CustomControl, then you should define all of your UI elements in XAML in the ControlTemplate for your control in generic.xaml. They will all be rendered automatically then and you won't need to refresh anything manually.

Also, you shouldn't really be using the OnRender method to do what you could simply do in XAML. That's more often used for defining new graphics for a control that are not already available for use from other UI elements in WPF. Just create DependencyProperties and data bind to any properties that you want to control in the UI. On thing to note is that you'll have to use a RelativeSource Binding to access them from generic.xaml (assuming you have added the GraphControls XAML Namespace Prefix):

<Ellipse Fill="{Binding MarkerFill, 
    RelativeSource={RelativeSource AncestorType={x:Type GraphControls:Marker}}}" />
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top