using System; using System.Collections.Specialized; using Xamarin.Forms.Internals; using ElmSharp; using EProgressBar = ElmSharp.ProgressBar; using ERect = ElmSharp.Rect; namespace Xamarin.Forms.Platform.Tizen { /// /// Renderer class for Xamarin ListView class. This provides necessary logic translating /// Xamarin API to Tizen Native API. This is a derivate of a ViewRenderer base class. /// This is a template class with two template parameters. First one is restricted to /// Xamarin.Forms.View and can be accessed via property Element. This represent actual /// xamarin view which represents control logic. Second one is restricted to ElmSharp.Widget /// types, and can be accessed with Control property. This represents actual native control /// which is used to draw control and realize xamarin forms api. /// public class ListViewRenderer : ViewRenderer { IListViewController Controller => Element; ITemplatedItemsView TemplatedItemsView => Element; /// /// The _lastSelectedItem and _selectedItemChanging are used for realizing ItemTapped event. Since Xamarin /// needs information only when an item has been taped, native handlers need to be agreagated /// and NotifyRowTapped has to be realized with this. /// GenListItem _lastSelectedItem = null; int _selectedItemChanging = 0; /// /// Initializes a new instance of the class. /// Note that at this stage of construction renderer dose not have required native element. This should /// only be used with xamarin engine. /// public ListViewRenderer() { RegisterPropertyHandler(ListView.IsGroupingEnabledProperty, UpdateIsGroupingEnabled); RegisterPropertyHandler(ListView.HasUnevenRowsProperty, UpdateHasUnevenRows); RegisterPropertyHandler(ListView.RowHeightProperty, UpdateRowHeight); RegisterPropertyHandler(ListView.SelectedItemProperty, UpdateSelectedItem); RegisterPropertyHandler(ListView.ItemsSourceProperty, UpdateSource); RegisterPropertyHandler("HeaderElement", UpdateHeader); RegisterPropertyHandler("FooterElement", UpdateFooter); } /// /// Invoked on creation of new ListView renderer. Handles the creation of a native /// element and initialization of the renderer. /// /// . protected override void OnElementChanged(ElementChangedEventArgs e) { if (Control == null) { SetNativeControl(new Native.ListView(Forms.Context.MainWindow)); Control.ItemSelected += OnListViewItemSelected; Control.ItemUnselected += OnListViewItemUnselected; } if (e.OldElement != null) { e.OldElement.ScrollToRequested -= OnScrollToRequested; e.OldElement.TemplatedItems.GroupedCollectionChanged -= OnGroupedCollectionChanged; e.OldElement.TemplatedItems.CollectionChanged -= OnCollectionChanged; } if (e.NewElement != null) { Element.ScrollToRequested += OnScrollToRequested; Element.TemplatedItems.GroupedCollectionChanged += OnGroupedCollectionChanged; Element.TemplatedItems.CollectionChanged += OnCollectionChanged; } base.OnElementChanged(e); } /// /// Handles the disposing of an existing renderer instance. Results in event handlers /// being detached and a Dispose() method from base class (VisualElementRenderer) being invoked. /// /// A boolean flag passed to the invocation of base class' Dispose() method. /// True if the memory release was requested on demand. protected override void Dispose(bool disposing) { if (disposing) { if (Element != null) { Element.ScrollToRequested -= OnScrollToRequested; Element.TemplatedItems.CollectionChanged -= OnCollectionChanged; Element.TemplatedItems.GroupedCollectionChanged -= OnGroupedCollectionChanged; } if (Control != null) { Control.ItemSelected -= OnListViewItemSelected; Control.ItemUnselected -= OnListViewItemUnselected; } } base.Dispose(disposing); } /// /// Handles item selected event. Note that it has to handle selection also for grouping mode as well. /// As a result of this method, ItemTapped event should be invoked in Xamarin. /// /// A native list instance from which the event has originated. /// Argument associated with handler, it holds native item for which event was raised void OnListViewItemSelected(object sender, GenListItemEventArgs e) { GenListItem item = e.Item; _lastSelectedItem = item; if (_selectedItemChanging == 0) { if (item != null) { int index = -1; if (Element.IsGroupingEnabled) { Native.ListView.ItemContext itemContext = item.Data as Native.ListView.ItemContext; if (itemContext.IsGroupItem) { return; } else { int groupIndex = (Element.TemplatedItems as System.Collections.IList).IndexOf(itemContext.ListOfSubItems); int inGroupIndex = itemContext.ListOfSubItems.IndexOf(itemContext.Cell); ++_selectedItemChanging; Element.NotifyRowTapped(groupIndex, inGroupIndex); --_selectedItemChanging; } } else { index = Element.TemplatedItems.IndexOf((item.Data as Native.ListView.ItemContext).Cell); ++_selectedItemChanging; Element.NotifyRowTapped(index); --_selectedItemChanging; } } } } /// /// Handles item unselected event. /// /// A native list instance from which the event has originated. /// Argument associated with handler, it holds native item for which event was raised void OnListViewItemUnselected(object sender, GenListItemEventArgs e) { if (_selectedItemChanging == 0) { _lastSelectedItem = null; } } /// /// This is method handles "scroll to" requests from xamarin events. /// It allows for scrolling to specified item on list view. /// /// A native list instance from which the event has originated. /// ScrollToRequestedEventArgs. void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e) { Cell cell; int position; var scrollArgs = (ITemplatedItemsListScrollToRequestedEventArgs)e; var templatedItems = TemplatedItemsView.TemplatedItems; if (Element.IsGroupingEnabled) { var results = templatedItems.GetGroupAndIndexOfItem(scrollArgs.Group, scrollArgs.Item); if (results.Item1 == -1 || results.Item2 == -1) return; var group = templatedItems.GetGroup(results.Item1); cell = group[results.Item2]; } else { position = templatedItems.GetGlobalIndexOfItem(scrollArgs.Item); cell = templatedItems[position]; } Control.ApplyScrollTo(cell, e.Position, e.ShouldAnimate); } /// /// Helper class for managing proper postion of Header and Footer element. /// Since both elements need to be implemented with ordinary list elements, /// both header and footer are removed at first, then the list is being modified /// and finally header and footer are prepended and appended to the list, respectively. /// class HeaderAndFooterHandler : IDisposable { VisualElement headerElement; VisualElement footerElement; Native.ListView Control; public HeaderAndFooterHandler(Widget control) { Control = control as Native.ListView; if (Control.HasHeader()) { headerElement = Control.GetHeader(); Control.RemoveHeader(); } if (Control.HasFooter()) { footerElement = Control.GetFooter(); Control.RemoveFooter(); } } public void Dispose() { if (headerElement != null) { Control.SetHeader(headerElement); } if (footerElement != null) { Control.SetFooter(footerElement); } } } /// /// This method is called whenever something changes in list view data model. /// Method will not be invoked for grouping mode, but for example event with /// action reset will be handled here when switching between group and no-group mode. /// /// TemplatedItemsList, Cell>. /// NotifyCollectionChangedEventArgs. void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { using (new HeaderAndFooterHandler(Control)) { if (e.Action == NotifyCollectionChangedAction.Add) { Cell before = null; if (e.NewStartingIndex + e.NewItems.Count < Element.TemplatedItems.Count) { before = Element.TemplatedItems[e.NewStartingIndex + e.NewItems.Count]; } Control.AddSource(e.NewItems, before); } else if (e.Action == NotifyCollectionChangedAction.Remove) { Control.Remove(e.OldItems); } else if (e.Action == NotifyCollectionChangedAction.Reset) { UpdateSource(); } } } /// /// This method is called whenever something changes in list view data model. /// Method will be invoked for grouping mode, but some action can be also handled /// by OnCollectionChanged handler. /// /// TemplatedItemsList, Cell>. /// NotifyCollectionChangedEventArgs. void OnGroupedCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { using (new HeaderAndFooterHandler(Control)) { if (e.Action == NotifyCollectionChangedAction.Add) { TemplatedItemsList, Cell> itemsGroup = sender as TemplatedItemsList, Cell>; Cell before = null; if (e.NewStartingIndex + e.NewItems.Count < itemsGroup.Count) { before = itemsGroup[e.NewStartingIndex + e.NewItems.Count]; } Control.AddItemsToGroup(itemsGroup, e.NewItems, before); } else if (e.Action == NotifyCollectionChangedAction.Remove) { Control.Remove(e.OldItems); } else if (e.Action == NotifyCollectionChangedAction.Reset) { Control.ResetGroup(sender as TemplatedItemsList, Cell>); } } } /// /// Updates the source. /// void UpdateSource() { Control.Clear(); Control.AddSource(Element.TemplatedItems); UpdateSelectedItem(); } /// /// Updates the header. /// void UpdateHeader() { Control.SetHeader(((IListViewController)Element).HeaderElement as VisualElement); } /// /// Updates the footer. /// void UpdateFooter() { Control.SetFooter(((IListViewController)Element).FooterElement as VisualElement); } /// /// Updates the has uneven rows. /// void UpdateHasUnevenRows() { Control.SetHasUnevenRows(Element.HasUnevenRows); } /// /// Updates the height of the row. /// void UpdateRowHeight(bool initialize) { if (initialize) return; Control.UpdateRealizedItems(); } /// /// Updates the is grouping enabled. /// /// If set to true, this method is invoked during initialization /// (otherwise it will be invoked only after property changes). void UpdateIsGroupingEnabled(bool initialize) { Control.IsGroupingEnabled = Element.IsGroupingEnabled; } /// /// Method is used for programaticaly selecting choosen item. /// void UpdateSelectedItem() { if (Element.SelectedItem == null) { if (_lastSelectedItem != null) { _lastSelectedItem.IsSelected = false; _lastSelectedItem = null; } } else { var templatedItems = TemplatedItemsView.TemplatedItems; var results = templatedItems.GetGroupAndIndexOfItem(Element.SelectedItem); if (results.Item1 != -1 && results.Item2 != -1) { var itemGroup = templatedItems.GetGroup(results.Item1); var cell = itemGroup[results.Item2]; ++_selectedItemChanging; Control.ApplySelectedItem(cell); --_selectedItemChanging; } } } } }