1

I was creating a custom control it is more like TabPage, where it's derive from View, containing a list of CSMenuItems and foreach menuItem is derived from BaseMenuItem and has menuContent that is derived from ContentView, like this :

• CSView
    • CSMenuItem
        • MenuContent
            • Content
    • CSMenuItem
        • MenuContent
            • Content

My problem is whenever I Change the properties value of MenuContent in the xaml file, the propertyChanged won't fire and MenuContent won't update. I am pretty sure the problem is in my renderers. Is there any way to update the child element in custom renderer?

Here are my codes for controls:

class CSView : View
{
    public CSView ()
    {
        var items = new ObservableCollection <CSMenuItem>();
        items.CollectionChanged += OnItemsChanged;
        Items = items;
    }

    public static readonly BindableProperty ItemsProperty = BindableProperty.Create ("Items", typeof (IList <CSMenuItem>), typeof (CSView));

    void OnItemsChanged (object sender, NotifyCollectionChangedEventArgs e)
    {
        foreach (CSMenuItem item in e.NewItems)
            item.Parent = this;
        // Maybe setting the item parent to this would be good?
        // cause whenever new item is add I want its parent to be this.
        // Correct me if im wrong.
    }
    
    public IList <CSMenuItem> Items 
    {
        get => (IList <CSMenuItem>)GetValue (ItemsProperty);
        set => SetValue (ItemsProperty, value);
    }

class CSMenuItem : BaseMenuItem
{
    public CSMenuItem()
    {
    }

    public static readonly BindableProperty TitleProperty = BindableProperty.Create("Title", typeof(string), typeof(CSMenuItem), default);
    public static readonly BindableProperty ContentProperty = BindableProperty.Create("Content", typeof(MenuContent), typeof(CSMenuItem));

    public string Title
    {
        get => (string)GetValue (TitleProperty);
        set => SetValue (TitleProperty, value);
    }
    public MenuContent Content
    {
        get => (MenuContent)GetValue (ContentProperty);
        set => SetValue (ContentProperty, value);
    }
}

class MenuContent : ContentView
{
    public MenuContent ()
    {
    }
    public static readonly BindableProperty TitleProperty = BindableProperty.Create ("Title", typeof (string), typeof (MenuContent));

    public string Title
    {
        get => (string)GetValue (TitleProperty);
        set => SetValue (TitleProperty, value);
    }
}

and for renderers :

class CSViewRenderer : ViewRenderer<CSView, FrameLayout>, NavigationBarView.IOnItemSelectedListener
{
    ...

    protected override void OnElementChanged (ElementChangedEventArgs <CSView> e)
    {
        if (e.OldElement != null)
            UnhandleEvents (e.OldElement);

        if (e.NewElement != null)
        {
            if (Control == null)
                SetNativeControl (_control);

            HandleEvents (e.NewElement)
        }
    }

    void HandleEvents (CSView element)
        => element.PropertyChanged += OnElementPropertyChanged;

    void UnhandleEvents (CSView element)
        => element.PropertyChanged -= OnElementPropertyChanged;
}

/// this is where the issue came from
class CSMenuItemRender : AbsItemRenderer
{
    ...
    protected override View CreateNativeControl ()
    {
        _base = new LinearLayout (Context);
        _toolBar = new ToolBar (Context)
        _content = Platform.CreateRendererWithContext (Element.Content, Context); // where Element is type of MenuItem

        _toolBar.Title = Element.Content.Title;
        _base.Orientation = Orientation.Vertical;

        _base.AddView (_toolBar, new LinearLayout.LayoutParams (-1, -2);
        _base.AddView (_content.View, new LinearLayout.LayoutParams (-1, -2);

        ElementRendererUtil.FitElement (Context, _base, _content);
        Platform.SetRenderer (Element.Content, _content);
        _content.ElementChanged += OnRendererElementChanged;

        return _base;
    }

    void OnElementChanged (ElementChangedEventArgs <CSMenuItem> e)
    {
        if (e.OldElement != null)
            UnhandleEvents (e.OldElement);

        if (e.NewElement != null)
        {
            if (Control == null)
                SetNativeControl (this)

            HandleEvents (e.NewElement);
        }
    }

    void OnRendererElementChanged (object sender, VisualElementChangedEventArgs e)
    {
        if (e.OldElement != null)
          e.OldElement.PropertyChanged -= OnRendererElementPropertyChanged;


        if (e.NewElement != null)
          e.NewElement.PropertyChanged += OnRendererElementPropertyChanged;
    }

    void OnRendererElementPropertyChanged (object sender, PropertyChangedEventArgs e)
    {
        var menuContent = (MenuContent)sender;

        if (e.PropertyName == MenuContent.TitleProperty.PropertyName)
            _toolBar.Title = menuContent.Title;

        else if (e.PropertyName == MenuContent.ContentProperty.PropertyName)
            _content.Tracker.UpdateLayout ();

    }

    void HandleEvents (CSMenuItem element)
        => element.PropertyChanged += OnElementPropertyChanged;

    void UnhandleEvents (CSMenuItem element)
        => element.PropertyChanged -= OnElementPropertyChanged;
}

I was able to change the MenuContent properties like Title when changing the derive type to Element, like this:

class MenuContent : Element

And setting its parent to CSMenuItem, like this :

public MenuContent Content
{
    ...
    set {
        if (value.Parent != this)
            value.Parent = this;
        SetValue (ContentProperty, value);
    }
}

but still the Content property of MenuContent won't update when the value is changed. What I want is to update the MenuContent what ever the value changed in xaml file and to know why the propertyChanged won't fire.

Sorry for my bad English, hope you understand. May the Almighty Bless You Stay safe.

  • There are no errors at all, it is completely fine. – CSharp Noob Nov 14 '22 at 11:46
  • Don't be curious about AbsItemRenderer it is a type fragment. I call it AbsItemRenderer cause it is in the renderer folder. – CSharp Noob Nov 14 '22 at 11:53
  • Perhaps access child element in `propertyChanged` parameter of [`BindableProperty.Create`](https://learn.microsoft.com/en-us/dotnet/api/xamarin.forms.bindableproperty.create?view=xamarin-forms). – ToolmakerSteve Nov 14 '22 at 20:42
  • Hmm, maybe you have point, I'll give it a try – CSharp Noob Nov 15 '22 at 12:59
  • You can also refer to [Custom control](https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/view) by official doc. It could be helpful to you. – Jianwei Sun - MSFT Nov 21 '22 at 02:54
  • @JianweiSun, thx for the infos, it is realy helpful to me but, what I want know is that to know why my child element property **MenuContent** won't update when it is change in xaml file – CSharp Noob Nov 24 '22 at 10:40
  • I am also curious that when I changed the derive type of **MenuContent** to **ContentView** it won't update when one of the properties is change, but when the derive type is changed to **Element** it will update properties like title when it is change in xaml. – CSharp Noob Nov 24 '22 at 10:47

0 Answers0