【问题标题】:How to set Xamarin.Forms Elements BindableProperties from a Custom Renderer?如何从自定义渲染器设置 Xamarin.Forms 元素 BindableProperties?
【发布时间】:2021-06-09 01:49:40
【问题描述】:

我一直在尝试通过自定义渲染器从我的本机控件中设置我的元素中的可绑定属性值。我的本机控件是一个视图 (painview),您可以在其中进行绘图,我正在尝试获取绘图并将其作为 base64 字符串设置为我的元素中的可绑定属性 Signature。

这是我的原生控件

public class PaintView : View
{
    Canvas _drawCanvas;
    Bitmap _canvasBitmap;
    readonly Paint _paint;
    readonly Dictionary<int, MotionEvent.PointerCoords> _coords = new Dictionary<int, MotionEvent.PointerCoords>();
    
    public Bitmap CanvasBitmap { get => _canvasBitmap; private set => _canvasBitmap = value; }

    private readonly string TAG = nameof(PaintView);

    public event EventHandler OnLineDrawn;

    public PaintView(Context context) : base(context, null, 0)
    {
        _paint = new Paint() { Color = Color.Blue, StrokeWidth = 5f, AntiAlias = true };
        _paint.SetStyle(Paint.Style.Stroke);
    }
    public PaintView(Context context, IAttributeSet attrs) : base(context, attrs) { }
    public PaintView(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle) { }

    protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
    {
        base.OnSizeChanged(w, h, oldw, oldh);
        _canvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888); // full-screen bitmap
        _drawCanvas = new Canvas(_canvasBitmap); // the canvas will draw into the bitmap
    }

    public override bool OnTouchEvent(MotionEvent e)
    {
        switch (e.ActionMasked)
        {
            case MotionEventActions.Down:
                {
                    int id = e.GetPointerId(0);
                    var start = new MotionEvent.PointerCoords();
                    e.GetPointerCoords(id, start);
                    _coords.Add(id, start);
                    return true;
                }
            case MotionEventActions.PointerDown:
                {
                    int id = e.GetPointerId(e.ActionIndex);
                    var start = new MotionEvent.PointerCoords();
                    e.GetPointerCoords(id, start);
                    _coords.Add(id, start);
                    return true;
                }
            case MotionEventActions.Move:
                {
                    for (int index = 0; index < e.PointerCount; index++)
                    {
                        var id = e.GetPointerId(index);
                        float x = e.GetX(index);
                        float y = e.GetY(index);
                        _drawCanvas.DrawLine(_coords[id].X, _coords[id].Y, x, y, _paint);
                        _coords[id].X = x;
                        _coords[id].Y = y;
                        OnLineDrawn?.Invoke(this, EventArgs.Empty);
                    }

                    Invalidate();
                    return true;
                }
            case MotionEventActions.PointerUp:
                {
                    int id = e.GetPointerId(e.ActionIndex);
                    _coords.Remove(id);
                    return true;
                }
            case MotionEventActions.Up:
                {
                    int id = e.GetPointerId(0);
                    _coords.Remove(id);
                    return true;
                }
            default:
                return false;
        }
    }

    protected override void OnDraw(Canvas canvas)
    {
        // Copy the off-screen canvas data onto the View from it's associated Bitmap (which stores the actual drawn data)
        canvas.DrawBitmap(_canvasBitmap, 0, 0, null);
    }

    public void Clear()
    {
        _drawCanvas.DrawColor(Color.Black, PorterDuff.Mode.Clear); // Paint the off-screen buffer black
        Invalidate(); // Call Invalidate to redraw the view
    }

    public void SetInkColor(Color color)
    {
        _paint.Color = color;
    }
}

属性 PaintView._canvasBitmap 是我想通过我的自定义渲染器在我的 Xamarin.Form 元素中设置的属性。

这是我的自定义渲染器

public class SketchViewRenderer : ViewRenderer<SketchView, PaintView>
{
    public SketchViewRenderer(Context context) : base(context) { }

    protected override void OnElementChanged(ElementChangedEventArgs<SketchView> e)
    {
        if (Control == null)
        {
            var paintView = new PaintView(Context);
            paintView.SetInkColor(Element.InkColor.ToAndroid());
            SetNativeControl(new PaintView(Context));
            MessagingCenter.Subscribe<SketchView>(this, nameof(SketchView.OnClear), OnMessageClear);
            Control.OnLineDrawn += PaintViewLineDrawn;
        }
    }

    private void PaintViewLineDrawn(object sender, EventArgs e)
    {
        var sketchCrl = (ISketchViewController)Element;

        if (sketchCrl == null) return;

        try
        {
            Element.SetValueFromRenderer(SketchView.SignatureProperty, Utils.Utils.BitmapToBase64(Control.CanvasBitmap));
            sketchCrl.SendSketchUpdated(Utils.Utils.BitmapToBase64(Control.CanvasBitmap));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

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

        if (e.PropertyName == SketchView.InkColorProperty.PropertyName)
        {
            Control.SetInkColor(Element.InkColor.ToAndroid());
        }
        if (e.PropertyName == SketchView.ClearProperty.PropertyName)
        {
            if (Element.Clear) OnMessageClear(Element);
        }
    }

    private void OnMessageClear(SketchView sender)
    {
        if (sender == Element) Control.Clear();            
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            MessagingCenter.Unsubscribe<SketchView>(this, nameof(SketchView.OnClear));
            Control.OnLineDrawn -= PaintViewLineDrawn;
        }

        base.Dispose(disposing);
    }
}

我尝试通过 SketchViewRenderer.PaintViewLineDrawn(...) 方法更改 Element.Signature 属性,但没有成功。这已经在调试我的视图模型时得到证明,其中属性没有按预期设置。

我的 Xamarin.Forms 元素如下所示

public class SketchView : View, IDoubleTappedController, ISketchViewController
{
    public static readonly BindableProperty SignatureProperty = BindableProperty.Create(nameof(Signature), typeof(string), typeof(SketchView), null, defaultBindingMode: BindingMode.TwoWay);
    public string Signature
    {
        get => (string)GetValue(SignatureProperty);
        set => SetValue(SignatureProperty, value);
    }

    public static readonly BindableProperty MultiTouchEnabledProperty = BindableProperty.Create(nameof(MultiTouchEnabled), typeof(bool), typeof(SketchView), false);
    public bool MultiTouchEnabled
    {
        get => (bool)GetValue(MultiTouchEnabledProperty);
        set => SetValue(MultiTouchEnabledProperty, value);
    }

    public static readonly BindableProperty InkColorProperty = BindableProperty.Create(nameof(InkColor), typeof(Xamarin.Forms.Color), typeof(SketchView), Xamarin.Forms.Color.Azure);
    public Xamarin.Forms.Color InkColor
    {
        get => (Xamarin.Forms.Color)GetValue(InkColorProperty);
        set => SetValue(InkColorProperty, value);
    }

    public static readonly BindableProperty ClearProperty = BindableProperty.Create(nameof(Clear), typeof(bool), typeof(SketchView), false, defaultBindingMode: BindingMode.TwoWay);
    public bool Clear
    {
        get => (bool)GetValue(ClearProperty);
        set
        {
            SetValue(ClearProperty, value);

            if (value) { OnClear(); }
        }
    }

    public void OnClear()
    {
        MessagingCenter.Send(this, nameof(OnClear));
    }

    public void SetSignature(string signature)
    {
        Signature = signature;
    }

    void IDoubleTappedController.DoubleTapped()
    {
        throw new NotImplementedException();
    }

    void ISketchViewController.SendSketchUpdated(string signature)
    {
        Clear = false;
        Signature = signature;
    }
}

我也尝试过使用自定义渲染器中的 SetValueFromRenderer() 方法,但没有成功。

您能否建议我从自定义渲染器中设置元素值的方法是什么?

感谢和亲切的问候,

节奏

【问题讨论】:

    标签: c# xamarin.forms mvvm custom-renderer


    【解决方案1】:

    问题是我的视图模型中的字段在与值进行比较时设置为空。然后抛出一个 TargetException 让源错误无法被目标更新。

    public bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = default)
        {
            if (value == null) return false;
            if (field != null && field.Equals(value)) return false;
    
            field = value;
    
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
            return true;
        }
    

    现在,我在使用 Equals 运算符之前确保该字段不为空。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-13
      • 2017-01-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-05
      • 2021-08-25
      • 1970-01-01
      相关资源
      最近更新 更多