Принудительная перерисовка представления Xamarin.Forms с помощью пользовательского средства визуализации

У меня есть визуальный элемент MyButton с пользовательским средством визуализации, реализованным для iOS.

Общий:

namespace RendererTest
{
    public class MyButton: Button
    {
        public Color BoundaryColor { get; set; }
    }

    public static class App
    {
        public static Page GetMainPage()
        {    
            var button = new MyButton { Text = "Click me!", BoundaryColor = Color.Red };
            button.Clicked += (sender, e) => (sender as MyButton).BoundaryColor = Color.Blue;
            return new ContentPage { Content = button };
        }
    }
}

iOS:

[assembly:ExportRenderer(typeof(MyButton), typeof(MyButtonRenderer))]

namespace RendererTest.iOS
{
    public class MyButtonRenderer: ButtonRenderer
    {
        public override void Draw(RectangleF rect)
        {
            using (var context = UIGraphics.GetCurrentContext()) {
                context.SetFillColor(Element.BackgroundColor.ToCGColor());
                context.SetStrokeColor((Element as MyButton).BoundaryColor.ToCGColor());
                context.SetLineWidth(10);
                context.AddPath(CGPath.FromRect(Bounds));
                context.DrawPath(CGPathDrawingMode.FillStroke);
            }
        }
    }
}

При нажатии кнопки красная граница должна стать синей. Видимо рендерер не замечает измененное свойство. Как я могу вызвать перерисовку?

(Этот пример для iOS. Но мой вопрос относится и к Android.)


person Falko    schedule 04.08.2014    source источник


Ответы (2)


Во-первых, превратите вас BoundaryColor в привязываемое свойство. Это не обязательно, достаточно запустить событие INPC, но тогда вы можете привязаться к нему:

public static readonly BindableProperty BoundaryColorProperty =
    BindableProperty.Create ("BoundaryColor", typeof(Color), typeof(MyButton), Color.Default);

public Color BoundaryColor {
    get { return (Color)GetValue (BoudaryColorProperty); }
    set { SetValue (BoundaryColorProperty, value); }
}

затем в вашем рендерере:

protected override void OnElementPropertyChanged (object sender, PropertyChangedEventArgs e)
{
    base.OnElementPropertyChanged (sender, e);

    if (e.PropertyName == MyButton.BoundaryColorProperty.PropertyName)
        SetNeedsDisplay ();
}
person Stephane Delcroix    schedule 05.08.2014
comment
Я пытался запустить public event Action BoundaryColorChanged при установке BoundaryColor. В конструкторе MyButtonRenderer я бы подписался на это событие и вызвал SetNeedsDisplay(). Однако, если я это сделаю, я получу System.Reflection.TargetInvocationException. - person Falko; 05.08.2014
comment
Это работает, если я подписываюсь не в конструкторе рендерера, а в методе Draw. Но это, вероятно, не лучшее решение, так как я буду продолжать добавлять (+=) слушателей к событию. - person Falko; 05.08.2014
comment
вам не нужно присоединяться к событию PropertyChanged, OnElementChanged уже подключено за вас, вам просто нужно его переопределить. - person Stephane Delcroix; 06.08.2014
comment
@StephaneDelcroix, что эквивалентно SetNeedsDisplay (); в андроиде? потому что мне нужно сделать это и на Android. - person Tomasz Kowalczyk; 23.08.2014

Требовались две модификации:

  1. Вызовите OnPropertyChanged в установщике свойства BoundaryColor:

    public class MyButton: Button
    {
        Color boundaryColor = Color.Red;
    
        public Color BoundaryColor {
            get {
                return boundaryColor;
            }
            set {
                boundaryColor = value;
                OnPropertyChanged();  // <-- here
            }
        }
    }
    
  2. Подпишитесь на событие в методе OnElementChanged MyButtonRenderer:

    public class MyButtonRenderer: ButtonRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
        {
            base.OnElementChanged(e);
            Element.PropertyChanged += (s_, e_) => SetNeedsDisplay();  // <-- here
        }
    
        public override void Draw(RectangleF rect)
        {
            // ...
        }
    }
    

Примечание. Кажется важным подписаться в OnElementChanged, а не в конструкторе. В противном случае поднимается System.Reflection.TargetInvocationException.

person Falko    schedule 05.08.2014
comment
Что было бы эквивалентно вызову SetNeedsDisplay() на Android? - person kaolick; 15.10.2014
comment
@kaolick: Это будет Invalidate(). - person Falko; 15.10.2014