Dispose () удаляет все дочерние элементы из средства визуализации платформы? - PullRequest
0 голосов
/ 02 октября 2019

Я добавил интерфейс IDisposable к одному из моих DatatTemplate s и реализовал соответствующие методы. В исходном коде плагина Dispose() вызывается для свойства View подкласса UIViewController. Но метод Dispose() моего DataTemplate никогда не вызывается. Я не могу войти в вызов dispose, отладчик перемещается только на следующую строку.

Если я распечатываю тип с помощью View.GetType(), я получаю следующий результат

Xamarin.Forms.Platform.iOS.Platform + DefaultRenderer

Если я посмотрю на свойство View (наведите курсор мыши), появится дочернее свойство с именем Element, которое соответствует моему типу DataTemplate. Разве Dispose() не вызывается для всех элементов child ? Почему это так и как мне добиться, чтобы мой пользовательский Dispose() метод моего DataTemplate вызывался?

Пример кода:

MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:TestCarouselView"
             xmlns:controls="clr-namespace:CarouselView.FormsPlugin.Abstractions;assembly=CarouselView.FormsPlugin.Abstractions"
             x:Class="TestCarouselView.MainPage">

    <controls:CarouselViewControl x:Name="carouselView" Orientation="Horizontal" InterPageSpacing="10" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">

    </controls:CarouselViewControl>

</ContentPage>

MainPage.xaml.cs

public partial class MainPage : ContentPage
{
    private int maxValue = 3;
    private CustomViewModel model;
    private DataTemplate template;

    public MainPage()
    {
        InitializeComponent();

        this.model = new CustomViewModel(this.maxValue);
        this.template = new DataTemplate(CreateDataTemplateView);

        this.carouselView.ItemsSource = this.model.ObjectList;
        this.carouselView.ItemTemplate = this.template;
        this.carouselView.Position = this.maxValue;
        this.carouselView.PositionSelected += CarouselView_PositionSelected;
    }

    private void CarouselView_PositionSelected(object sender, CarouselView.FormsPlugin.Abstractions.PositionSelectedEventArgs e)
    {
        var currentObject = this.model.ObjectList.ElementAt(e.NewValue);
        var nextToLastObject = this.model.ObjectList.ElementAt(this.model.ObjectList.Count - 2);
        var secondObject = this.model.ObjectList.ElementAt(1);

        if (currentObject == nextToLastObject)
        {
            this.SwipeAppend();
        }
        else if (currentObject == secondObject)
        {
            this.SwipePrepend();
        }
    }

    private View CreateDataTemplateView()
    {
        CustomTemplateView template = new CustomTemplateView();
        template.BindingContext = this.model.ObjectList;
        // here I'm subscribing to events from the DataTemplate
        template.ElementSelected += OnElementSelected;

        return (View)template;
    }

    public void SwipeAppend()
    {
        int newId = Int32.Parse(this.model.ObjectList[this.model.ObjectList.Count - 1].Id) + 1;
        this.model.ObjectList.Add(new CustomObject(newId.ToString()));
        this.model.ObjectList.RemoveAt(0);
    }

    public void SwipePrepend()
    {
        int oldPosition = this.carouselView.Position;
        int oldId = Int32.Parse(this.model.ObjectList[0].Id);
        this.model.ObjectList.Insert(0, new CustomObject((oldId - 1).ToString()));
        this.model.ObjectList.RemoveAt(this.model.ObjectList.Count - 1);
        this.carouselView.Position = oldPosition + 1;
    }
}

CustomViewModel.cs

public class CustomViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableCollection<CustomObject> ObjectList { get; set; }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public CustomViewModel(int amount)
    {
        ObjectList = new ObservableCollection<CustomObject>();

        for (int i = -amount; i <= amount; i++)
        {
            ObjectList.Add(new CustomObject(i.ToString()));
        }
    }
}

CustomObject.cs

public class CustomObject
{
    private string id;

    public string Id
    {
        get { return this.id; }
        set { this.id = value; }
    }

    public CustomObject(string id)
    {
        this.id = id;
    }
}

CustomTemplateView.xaml

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="TestCarouselView.CustomTemplateView">
    <ContentView.Content>
        <skia:SKCanvasView
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0,0,1,1"
            x:Name="drawingArea"
            IgnorePixelScaling="True" />
    </ContentView.Content>
</ContentView>

CustomTemplateView.xaml.cs

public partial class CustomTemplateView : ContentView, IDisposable
{
    private CustomObject myData;
    public CustomTemplateView()
    {
        InitializeComponent();

        // here I'm subscribing to MessagingCenter
        MessagingCenter.Subscribe<SelectElement>(this, Msg_SelectElement, message =>
        {
            this.SelectElement(message.Element);
        });
    }

    protected override void OnBindingContextChanged()
    {
        base.OnBindingContextChanged();

        this.myData = this.BindingContext as CustomObject;
        if (this.myData != null)
        {
            this.SetupContent();
        }
    }

    private void SetupContent()
    {
        this.drawingArea.PaintSurface += drawingArea_PaintSurface;
    }

    private void drawingArea_PaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        args.Surface.Canvas.Clear();

        SKPaint textPaint = new SKPaint
        {
            IsAntialias = true,
            Style = SKPaintStyle.Fill,
            Color = SKColors.Black,
            TextSize = 92,
            StrokeWidth = 1,
        };
        args.Surface.Canvas.DrawText(this.myData.Id, args.Info.Width/2, args.Info.Height/2, textPaint);
    }

    // Implement IDisposable. 
    // Do not make this method virtual. 
    // A derived class should not be able to override this method. 
    public void Dispose()
    {
        Dispose(true);
        // This object will be cleaned up by the Dispose method. 
        // Therefore, you should call GC.SupressFinalize to 
        // take this object off the finalization queue 
        // and prevent finalization code for this object 
        // from executing a second time.
        GC.SuppressFinalize(this);
    }

    // Dispose(bool disposing) executes in two distinct scenarios. 
    // If disposing equals true, the method has been called directly 
    // or indirectly by a user's code. Managed and unmanaged resources 
    // can be disposed. 
    // If disposing equals false, the method has been called by the 
    // runtime from inside the finalizer and you should not reference 
    // other objects. Only unmanaged resources can be disposed. 
    protected virtual void Dispose(bool disposing)
    {
        System.Diagnostics.Debug.WriteLine("Dispose is called");

        // Check to see if Dispose has already been called. 
        if (!this.disposed)
        {
            // If disposing equals true, dispose all managed 
            // and unmanaged resources. 
            if (disposing)
            {
                // Dispose managed resources.
                this.drawingArea.PaintSurface -= drawingArea_PaintSurface;
                MessagingCenter.Unsubscribe<SelectElement>(this, Msg_SelectElement);
            }

            // Call the appropriate methods to clean up 
            // unmanaged resources here. 
            // If disposing is false, 
            // only the following code is executed.
            // ...

            // Note disposing has been done.
            disposed = true;
        }
    }

    // Use C# destructor syntax for finalization code. 
    // This destructor will run only if the Dispose method 
    // does not get called. 
    // It gives your base class the opportunity to finalize. 
    // Do not provide destructors in types derived from this class.
    ~CustomTemplateView()
    {
        System.Diagnostics.Debug.WriteLine("Finalizer is called");
        // Do not re-create Dispose clean-up code here. 
        // Calling Dispose(false) is optimal in terms of 
        // readability and maintainability.
        Dispose(false);
    }
}

Изменения в плагине CarouselView :

CarouselViewImplementation.cs

async Task RemovePage(int position)
{
    if (Element == null || pageController == null || Source == null) return;

    if (Source?.Count > 0)
    {
        // To remove latest page, rebuild pageController or the page wont disappear
        if (Source.Count == 1)
        {
            Source.RemoveAt(position);
            SetNativeView();
        }
        else
        {
            // ### Beginn of modification ###
            // Remove controller from ChildViewControllers
            if (ChildViewControllers != null)
            {
                ViewContainer child = ChildViewControllers.FirstOrDefault(c => c.Tag == Source[position]);
                if (child != null)
                {
                    ChildViewControllers.Remove(child);
                    child.Dispose();
                }
            }

            object elementToRemove = Source.ElementAt(position);
            //Source.RemoveAt(position);
            Source.Remove(elementToRemove);
            //elementToRemove.Dispose();

            // ### End of modification ###

            // To remove current page
            if (position == Element.Position)
            {
                var newPos = position - 1;
                if (newPos == -1)
                    newPos = 0;

                // With a swipe transition
                if (Element.AnimateTransition)
                    await Task.Delay(100);

                var direction = position == 0 ? UIPageViewControllerNavigationDirection.Forward : UIPageViewControllerNavigationDirection.Reverse;
                var firstViewController = CreateViewController(newPos);

                pageController.SetViewControllers(new[] { firstViewController }, direction, Element.AnimateTransition, s =>
                {
                    isChangingPosition = true;
                    Element.Position = newPos;
                    isChangingPosition = false;

                    SetArrowsVisibility();
                    SetIndicatorsCurrentPage();

                    // Invoke PositionSelected as DidFinishAnimating is only called when touch to swipe
                    Element.SendPositionSelected();
                    Element.PositionSelectedCommand?.Execute(null);
                });
            }
            else
            {
                var firstViewController = pageController.ViewControllers[0];

                pageController.SetViewControllers(new[] { firstViewController }, UIPageViewControllerNavigationDirection.Forward, false, s =>
                {
                    SetArrowsVisibility();
                    SetIndicatorsCurrentPage();

                    // Invoke PositionSelected as DidFinishAnimating is only called when touch to swipe
                    Element.SendPositionSelected();
                    Element.PositionSelectedCommand?.Execute(null);
                });
            }
        }

        prevPosition = Element.Position;
    }
}

UIViewController CreateViewController(int index)
{
    // ### Beginn of modification ###
    if (ChildViewControllers == null)
        ChildViewControllers = new List<ViewContainer>();
    // ### End of modification ###

    // Significant Memory Leak for iOS when using custom layout for page content #125
    var newTag = Source[index];
    foreach (ViewContainer child in pageController.ChildViewControllers)
    {
        if (child.Tag == newTag)
            return child;
    }

    View formsView = null;

    object bindingContext = null;

    if (Source != null && Source?.Count > 0)
        bindingContext = Source.Cast<object>().ElementAt(index);

    var dt = bindingContext as DataTemplate;
    // Support for List<View> as ItemsSource
    var view = bindingContext as View;

    // Support for List<DataTemplate> as ItemsSource
    if (dt != null)
    {
        formsView = (View)dt.CreateContent();
    }
    else
    {
        if (view != null)
        {
            if (ChildViewControllers == null)
                ChildViewControllers = new List<ViewContainer>();

            // Return from the local copy of controllers
            foreach(ViewContainer controller in ChildViewControllers)
            {
                if (controller.Tag == view)
                {
                    return controller;
                }
            }

            formsView = view;
        }
        else
        {
            var selector = Element.ItemTemplate as DataTemplateSelector;
            if (selector != null)
                formsView = (View)selector.SelectTemplate(bindingContext, Element).CreateContent();
            else
                formsView = (View)Element.ItemTemplate.CreateContent();

            formsView.BindingContext = bindingContext;
        }
    }

    // HeightRequest fix
    formsView.Parent = this.Element;

    // UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height
    var rect = new CGRect(Element.X, Element.Y, ElementWidth, ElementHeight);
    var nativeConverted = formsView.ToiOS(rect);

    //if (dt == null && view == null)
        //formsView.Parent = null;

    var viewController = new ViewContainer();
    viewController.Tag = bindingContext;
    viewController.View = nativeConverted;

    // Only happens when ItemsSource is List<View>
    if (ChildViewControllers != null)
    {
        ChildViewControllers.Add(viewController);
        Console.WriteLine("ChildViewControllers count = " + ChildViewControllers.Count());
    }

    return viewController;
}
...