Prism2 Region Adapter for AvalonDock's DocumentPane and DockingPane? - prism

Does anyone have sample code on how to create a Region Adapter for AvalonDock's DocumentPane and DockingPane?

The Markus Raufer has added two region adapters to the CompositeWpfContrib project at CodePlex that supports both DocumentPane and DockingPane.

I have used Raffaeu Bermuda snippets to support Avalon tab region adapter, but found that there is some issues are not solved:
1- It does not support Activating a a certain view (aka - tab - DockableContent), so the code Region.Activate(object view) will not work.
2- All the Tabs are active by default in the region. So Region.ActiveViews collection by default has all the views, this is not ideal, as sometimes I needed to verify if a view is active or not (you could imagine a save button on a tool bar region that executes a SaveCommand only on the current active view = tab in our case)
3- Closed views doesn't actually get closed, only hidden. Even if you set the HideOnClose = true when adding the newDockableContent, it is still not removed from Region.Views collection. This could lead to memory leaks issues.
4- If you have a previously added DockableContent in the Pane, they will not get synchronized and added to the Region.Views collection.
So here are the code I am using now, it is just a small tweak from the Selector Adapter and Selector Sync Behavior found in PRISM source code:
AvalonRegionAdapter Class:
public class AvalonRegionAdapter : RegionAdapterBase<DocumentPane>
{
public AvalonRegionAdapter(IRegionBehaviorFactory factory) : base(factory) {}
protected override void AttachBehaviors(IRegion region, DocumentPane regionTarget)
{
if (region == null) throw new System.ArgumentNullException("region");
//Add the behavior that syncs the items source items with the rest of the items
region.Behaviors.Add(AvalonDocumentSyncBehavior.BehaviorKey,
new AvalonDocumentSyncBehavior()
{
HostControl = regionTarget
});
base.AttachBehaviors(region, regionTarget);
}
protected override void Adapt(IRegion region, DocumentPane regionTarget){ }
protected override IRegion CreateRegion()
{
return new Region();
}
}
AvalonDocumentSyncBehavior Behavior Code:
public class AvalonDocumentSyncBehavior : RegionBehavior, IHostAwareRegionBehavior
{
/// <summary>
/// Name that identifies the SelectorItemsSourceSyncBehavior behavior in a collection of RegionsBehaviors.
/// </summary>
public static readonly string BehaviorKey = "AvalonDocumentSyncBehavior";
private bool updatingActiveViewsInHostControlSelectionChanged;
private Selector hostControl;
/// <summary>
/// Gets or sets the <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
/// </summary>
/// <value>
/// A <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
/// </value>
/// <remarks>For this behavior, the host control must always be a <see cref="Selector"/> or an inherited class.</remarks>
public DependencyObject HostControl
{
get
{
return this.hostControl;
}
set
{
this.hostControl = value as Selector;
}
}
/// <summary>
/// Starts to monitor the <see cref="IRegion"/> to keep it in synch with the items of the <see cref="HostControl"/>.
/// </summary>
protected override void OnAttach()
{
bool itemsSourceIsSet = this.hostControl.ItemsSource != null;
if (itemsSourceIsSet)
{
//throw new InvalidOperationException(Resources.ItemsControlHasItemsSourceException);
}
this.SynchronizeItems();
this.hostControl.SelectionChanged += this.HostControlSelectionChanged;
this.Region.ActiveViews.CollectionChanged += this.ActiveViews_CollectionChanged;
this.Region.Views.CollectionChanged += this.Views_CollectionChanged;
}
private void Views_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
int startIndex = e.NewStartingIndex;
foreach (object newItem in e.NewItems)
{
UIElement view = newItem as UIElement;
TabViewModel viewModel = ((UserControl)view).DataContext as TabViewModel;
if (view != null)
{
DockableContent newDockableContent = new DockableContent();
newDockableContent.Content = newItem;
//if associated view has metadata then apply it.
newDockableContent.Title = view.GetType().ToString();
if (viewModel != null)
{
//Image img = new Image();
//img.Source = new BitmapImage(new Uri(#"Resources/Alerts.png", UriKind.Relative));
newDockableContent.Title = viewModel.TabModel.Title;
newDockableContent.IsCloseable = viewModel.TabModel.CanClose;
//newContentPane.Icon = img.Source;
}
//When contentPane is closed remove from the associated region
newDockableContent.Closed += new EventHandler(newDockableContent_Closed);
newDockableContent.HideOnClose = false;
this.hostControl.Items.Add(newDockableContent);
newDockableContent.Activate();
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (object oldItem in e.OldItems)
{
this.hostControl.Items.Remove(oldItem);
}
}
}
void newDockableContent_Closed(object sender, EventArgs e)
{
var dockableContent = sender as DockableContent;
if(dockableContent != null)
if (this.Region.Views.Contains(dockableContent.Content))
{
this.Region.Remove(dockableContent.Content);
}
}
private void SynchronizeItems()
{
List<object> existingItems = new List<object>();
// Control must be empty before "Binding" to a region
foreach (object childItem in this.hostControl.Items)
{
existingItems.Add(childItem);
}
foreach (object view in this.Region.Views)
{
this.hostControl.Items.Add(view);
}
foreach (object existingItem in existingItems)
{
this.Region.Add(existingItem);
}
}
private void ActiveViews_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.updatingActiveViewsInHostControlSelectionChanged)
{
// If we are updating the ActiveViews collection in the HostControlSelectionChanged, that
// means the user has set the SelectedItem or SelectedItems himself and we don't need to do that here now
return;
}
if (e.Action == NotifyCollectionChangedAction.Add)
{
var selectedDockableContent = this.hostControl.SelectedItem as DockableContent;
if (selectedDockableContent != null
&& selectedDockableContent.Content != null
&& selectedDockableContent.Content != e.NewItems[0]
&& this.Region.ActiveViews.Contains(selectedDockableContent.Content))
{
this.Region.Deactivate(selectedDockableContent.Content);
}
var _UIElement = e.NewItems[0] as FrameworkElement;
this.hostControl.SelectedItem = _UIElement.Parent;
}
else if (e.Action == NotifyCollectionChangedAction.Remove &&
e.OldItems.Contains(this.hostControl.SelectedItem))
{
this.hostControl.SelectedItem = null;
}
}
private void HostControlSelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
// Record the fact that we are now updating active views in the HostControlSelectionChanged method.
// This is needed to prevent the ActiveViews_CollectionChanged() method from firing.
this.updatingActiveViewsInHostControlSelectionChanged = true;
object source;
source = e.OriginalSource;
if (source == sender)
{
foreach (object item in e.RemovedItems)
{
// check if the view is in both Views and ActiveViews collections (there may be out of sync)
var dockableContent = item as DockableContent;
if (this.Region.Views.Contains(dockableContent.Content) && this.Region.ActiveViews.Contains(dockableContent.Content))
{
this.Region.Deactivate(dockableContent.Content);
}
}
foreach (object item in e.AddedItems)
{
var dockableContent = item as DockableContent;
if (this.Region.Views.Contains(dockableContent.Content) &&
!this.Region.ActiveViews.Contains(dockableContent.Content))
{
this.Region.Activate(dockableContent.Content);
}
}
}
}
finally
{
this.updatingActiveViewsInHostControlSelectionChanged = false;
}
}
}
Code on bootstrapper to configure the Adapter
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
var mappings = base.ConfigureRegionAdapterMappings();
mappings.RegisterMapping(typeof(AvalonDock.DocumentPane),
this.Container.Resolve<AvalonRegionAdapter>());
return mappings;
}
Then you need the TabModel and the TabViewModel as fromRaffaeu Bermuda
public sealed class TabModel : DependencyObject
{
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
// Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(TabModel));
public bool CanClose
{
get { return (bool)GetValue(CanCloseProperty); }
set { SetValue(CanCloseProperty, value); }
}
// Using a DependencyProperty as the backing store for CanClose. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CanCloseProperty =
DependencyProperty.Register("CanClose", typeof(bool), typeof(TabModel));
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
set { SetValue(IsModifiedProperty, value); }
}
// Using a DependencyProperty as the backing store for IsModified. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(TabModel));
}
And a TabViewModel acting as a base class:
public class TabViewModel : INotifyPropertyChanged
{
private TabModel _tabModel;
public TabModel TabModel
{
get { return this._tabModel; }
set
{
this._tabModel = value;
OnPropertyChanged("TabModel");
}
}
public TabViewModel()
{
this.TabModel = new TabModel();
this.TabModel.CanClose = true;
this.TabModel.IsModified = false;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Let me know if you need further help, I will post a blog this in the near future.

Since the Avalon DocumentPane and DockingPane are both based on the System.Windows.Controls.Primitives.Selector you can use the default SelectorRegionAdapter in Prism.
Just base your control on DockableContent
<ad:DockableContent x:Class="DesignerWPF.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock"
d:DesignHeight="300" d:DesignWidth="300" Title="dans">
<Grid>
<TextBox Text="sdfdf"></TextBox>
</Grid>
</ad:DockableContent>
on your main Shell.xmal set the regions in the dockablepane
<ad:DockingManager x:Name="dockManager" Grid.Row="1" Margin="0,4,0,0">
<ad:ResizingPanel Orientation="Horizontal">
<ad:DockablePane cal:RegionManager.RegionName="LeftRegion">
</ad:DockablePane>
<ad:DocumentPane cal:RegionManager.RegionName="DocumentRegion">
</ad:DocumentPane>
</ad:ResizingPanel>
</ad:DockingManager>
then when you initialize your presenter for your control it will get displayed in the dock.
public class UserTestControl : IModule
{
public UserTestControl(IUnityContainer container, IRegionManager regionManager)
{
Container = container;
RegionManager = regionManager;
}
public void Initialize()
{
var addFundView = Container.Resolve<UserControl1>();
RegionManager.Regions["LeftRegion"].Add(addFundView);
}
public IUnityContainer Container { get; private set; }
public IRegionManager RegionManager { get; private set; }
}

My advice would be to look in Microsoft.Practices.Composite.Presentation.Regions in the Prism source. Specifically, take a look at the ItemsControlRegionAdapter and use it as a template. Remember to inherit from RegionAdapterBase<>:
public class ItemsControlRegionAdapter : RegionAdapterBase<ItemsControl>
and to override ConfigureRegionAdapterMappings() in the bootstrapper. That would look something like:
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
RegionAdapterMappings mappings = base.ConfigureRegionAdapterMappings();
mappings.RegisterMapping(typeof(Canvas), Container.Resolve<CanvasRegionAdapter>());
return mappings;
}

Related

How to optimize xamarin Treeview performence?

So long story short, I wanted to create a treeview control but I got hit by a huge performence problem when there is many items. that was mainly because of 2 things, but I just want to ask you now for one.
So I have an ObservableCollection of type TreeViewNode that inherits from StackLayout (This is my treeView item source)
This the treeviewnode class :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Text;
using Xamarin.Forms;
namespace Involys.PraxisDemo.Controls.InCustomTreeView
{
public class TreeViewNode : StackLayout
{
#region Image source for icons
private DataTemplate _ExpandButtonTemplate = null;
#endregion
#region Fields
private TreeViewNode _ParentTreeViewItem;
private DateTime _ExpandButtonClickedTime;
private readonly BoxView _SpacerBoxView = new BoxView();
private readonly BoxView _EmptyBox = new BoxView { BackgroundColor = Color.Blue, Opacity = .5 };
private const int ExpandButtonWidth = 32;
private ContentView _ExpandButtonContent = new ContentView();
private readonly Grid _MainGrid = new Grid
{
VerticalOptions = LayoutOptions.StartAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
RowSpacing = 2
};
private readonly StackLayout _ContentStackLayout = new StackLayout { Orientation = StackOrientation.Horizontal };
private readonly ContentView _ContentView = new ContentView
{
HorizontalOptions = LayoutOptions.FillAndExpand
};
private readonly StackLayout _ChildrenStackLayout = new StackLayout
{
Orientation = StackOrientation.Vertical,
Spacing = 0,
IsVisible = false
};
private IList<TreeViewNode> _Children = new ObservableCollection<TreeViewNode>();
private readonly TapGestureRecognizer _TapGestureRecognizer = new TapGestureRecognizer();
private readonly TapGestureRecognizer _ExpandButtonGestureRecognizer = new TapGestureRecognizer();
private readonly TapGestureRecognizer _DoubleClickGestureRecognizer = new TapGestureRecognizer();
#endregion
#region Internal Fields
internal readonly BoxView SelectionBoxView = new BoxView { Color = Color.Red, Opacity = .5, IsVisible = false };
#endregion
#region Private Properties
private TreeView ParentTreeView => Parent?.Parent as TreeView;
private double IndentWidth => Depth * SpacerWidth;
private int SpacerWidth { get; } = 30;
private int Depth => ParentTreeViewItem?.Depth + 1 ?? 0;
private bool _ShowExpandButtonIfEmpty = false;
private Color _SelectedBackgroundColor = Color.Blue;
private double _SelectedBackgroundOpacity = .3;
#endregion
#region Events
public event EventHandler Expanded;
/// <summary>
/// Occurs when the user double clicks on the node
/// </summary>
public event EventHandler DoubleClicked;
#endregion
#region Protected Overrides
protected override void OnParentSet()
{
base.OnParentSet();
Render();
}
#endregion
#region Public Properties
public string NodeKey { get; set; }
public int NodeId { get; set; }
public bool IsSelected
{
get => SelectionBoxView.IsVisible;
set => SelectionBoxView.IsVisible = value;
}
public bool IsExpanded
{
get => _ChildrenStackLayout.IsVisible;
set
{
_ChildrenStackLayout.IsVisible = value;
Render();
if (value)
{
Expanded?.Invoke(this, new EventArgs());
}
}
}
/// <summary>
/// set to true to show the expand button in case we need to poulate the child nodes on demand
/// </summary>
public bool ShowExpandButtonIfEmpty
{
get { return _ShowExpandButtonIfEmpty; }
set { _ShowExpandButtonIfEmpty = value; }
}
/// <summary>
/// set BackgroundColor when node is tapped/selected
/// </summary>
public Color SelectedBackgroundColor
{
get { return _SelectedBackgroundColor; }
set { _SelectedBackgroundColor = value; }
}
/// <summary>
/// SelectedBackgroundOpacity when node is tapped/selected
/// </summary>
public Double SelectedBackgroundOpacity
{
get { return _SelectedBackgroundOpacity; }
set { _SelectedBackgroundOpacity = value; }
}
/// <summary>
/// customize expand icon based on isExpanded property and or data
/// </summary>
public DataTemplate ExpandButtonTemplate
{
get { return _ExpandButtonTemplate; }
set { _ExpandButtonTemplate = value; }
}
public View Content
{
get => _ContentView.Content;
set => _ContentView.Content = value;
}
public IList<TreeViewNode> Children
{
get => _Children;
set
{
if (_Children is INotifyCollectionChanged notifyCollectionChanged)
{
notifyCollectionChanged.CollectionChanged -= ItemsSource_CollectionChanged;
}
_Children = value;
if (_Children is INotifyCollectionChanged notifyCollectionChanged2)
{
notifyCollectionChanged2.CollectionChanged += ItemsSource_CollectionChanged;
}
TreeView.RenderNodes(_Children, _ChildrenStackLayout, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset), this);
Render();
}
}
/// <summary>
/// TODO: Remove this. We should be able to get the ParentTreeViewNode by traversing up through the Visual Tree by 'Parent', but this not working for some reason.
/// </summary>
public TreeViewNode ParentTreeViewItem
{
get => _ParentTreeViewItem;
set
{
_ParentTreeViewItem = value;
Render();
}
}
#endregion
#region Constructor
/// <summary>
/// Constructs a new TreeViewItem
/// </summary>
public TreeViewNode()
{
var itemsSource = (ObservableCollection<TreeViewNode>)_Children;
itemsSource.CollectionChanged += ItemsSource_CollectionChanged;
_TapGestureRecognizer.Tapped += TapGestureRecognizer_Tapped;
GestureRecognizers.Add(_TapGestureRecognizer);
_MainGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
_MainGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
_MainGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
_MainGrid.Children.Add(SelectionBoxView);
_ContentView.GestureRecognizers.Add(_TapGestureRecognizer);
_ContentStackLayout.Children.Add(_SpacerBoxView);
_ContentStackLayout.Children.Add(_ExpandButtonContent);
_ContentStackLayout.Children.Add(_ContentView);
SetExpandButtonContent(_ExpandButtonTemplate);
_ExpandButtonGestureRecognizer.Tapped += ExpandButton_Tapped;
_ExpandButtonContent.GestureRecognizers.Add(_ExpandButtonGestureRecognizer);
_DoubleClickGestureRecognizer.NumberOfTapsRequired = 2;
_DoubleClickGestureRecognizer.Tapped += DoubleClick;
_ContentView.GestureRecognizers.Add(_DoubleClickGestureRecognizer);
_MainGrid.Children.Add(_ContentStackLayout);
_MainGrid.Children.Add(_ChildrenStackLayout, 0, 1);
base.Children.Add(_MainGrid);
HorizontalOptions = LayoutOptions.FillAndExpand;
VerticalOptions = LayoutOptions.Start;
Render();
}
void _DoubleClickGestureRecognizer_Tapped(object sender, EventArgs e)
{
}
#endregion
#region Private Methods
/// <summary>
/// TODO: This is a little stinky...
/// </summary>
private void ChildSelected(TreeViewNode child)
{
//Um? How does this work? The method here is a private method so how are we calling it?
ParentTreeViewItem?.ChildSelected(child);
ParentTreeView?.ChildSelected(child);
}
private void Render()
{
_SpacerBoxView.WidthRequest = IndentWidth;
if ((Children == null || Children.Count == 0) && !ShowExpandButtonIfEmpty)
{
SetExpandButtonContent(_ExpandButtonTemplate);
return;
}
SetExpandButtonContent(_ExpandButtonTemplate);
foreach (var item in Children)
{
item.Render();
}
}
/// <summary>
/// Use DataTemplae
/// </summary>
private void SetExpandButtonContent(DataTemplate expandButtonTemplate)
{
if (expandButtonTemplate != null)
{
_ExpandButtonContent.Content = (View)expandButtonTemplate.CreateContent();
}
else
{
_ExpandButtonContent.Content = (View)new ContentView { Content = _EmptyBox };
}
}
#endregion
#region Event Handlers
private void ExpandButton_Tapped(object sender, EventArgs e)
{
_ExpandButtonClickedTime = DateTime.Now;
IsExpanded = !IsExpanded;
}
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
//TODO: Hack. We don't want the node to become selected when we are clicking on the expanded button
// if (DateTime.Now - _ExpandButtonClickedTime > new TimeSpan(0, 0, 0, 0, 50))
// {
ChildSelected(this);
// }
}
private void DoubleClick(object sender, EventArgs e)
{
DoubleClicked?.Invoke(this, new EventArgs());
}
private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
TreeView.RenderNodes(_Children, _ChildrenStackLayout, e, this);
Render();
}
#endregion
}
}
and the TreeView control :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using Xamarin.Forms;
namespace Involys.PraxisDemo.Controls.InCustomTreeView
{
public class TreeView : ScrollView
{
#region Fields
private static StackLayout _StackLayout = new StackLayout { Orientation = StackOrientation.Vertical };
//TODO: This initialises the list, but there is nothing listening to INotifyCollectionChanged so no nodes will get rendered
private static ObservableCollection<TreeViewNode> _RootNodes = new ObservableCollection<TreeViewNode>();
private TreeViewNode _SelectedItem;
#endregion
#region Public Properties
/// <summary>
/// The item that is selected in the tree
/// TODO: Make this two way - and maybe eventually a bindable property
/// </summary>
public static readonly BindableProperty RootNodesProperty =
BindableProperty.Create(nameof(RootNodes), typeof(IEnumerable<TreeViewNode>), typeof(TreeView), null,
BindingMode.Default, null, OnRootNodesChanged);
public static readonly BindableProperty SelectedItemProperty =
BindableProperty.Create(nameof(SelectedItem), typeof(TreeViewNode), typeof(TreeView), null,
BindingMode.TwoWay);
public IEnumerable<TreeViewNode> RootNodes
{
get
{
return (IEnumerable<TreeViewNode>)GetValue(RootNodesProperty);
}
set
{
SetValue(RootNodesProperty, value);
}
}
public TreeViewNode SelectedItem
{
get { return (TreeViewNode)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
#endregion
#region Events
/// <summary>
/// Occurs when the user selects a TreeViewItem
/// </summary>
public event EventHandler SelectedItemChanged;
#endregion
#region Constructor
public TreeView()
{
_StackLayout = new StackLayout { Orientation = StackOrientation.Vertical };
Content = _StackLayout;
}
#endregion
//selection
#region Internal Methods
/// <summary>
/// TODO: A bit stinky but better than bubbling an event up...
/// </summary>
internal void ChildSelected(TreeViewNode child)
{
SelectedItem = child;
child.IsSelected = true;
child.SelectionBoxView.Color = child.SelectedBackgroundColor;
child.SelectionBoxView.Opacity = child.SelectedBackgroundOpacity;
RemoveSelectionRecursive(RootNodes);
}
#endregion
#region Private Methods
private void RemoveSelectionRecursive(IEnumerable<TreeViewNode> nodes)
{
foreach (var treeViewItem in nodes)
{
if (treeViewItem != SelectedItem)
{
treeViewItem.IsSelected = false;
}
RemoveSelectionRecursive(treeViewItem.Children);
}
}
#endregion
//end selection
#region Internal Static Methods
internal static void RenderNodes(IEnumerable<TreeViewNode> childTreeViewItems, StackLayout parent, NotifyCollectionChangedEventArgs e, TreeViewNode parentTreeViewItem)
{
if (e.Action != NotifyCollectionChangedAction.Add)
{
//TODO: Reintate this...
parent.Children.Clear();
AddItems(childTreeViewItems, parent, parentTreeViewItem);
}
else
{
AddItems(e.NewItems.Cast<TreeViewNode>(), parent, parentTreeViewItem);
}
}
static void OnRootNodesChanged(BindableObject bindable, object oldvalue, object newvalue)
{
System.Diagnostics.Debug.WriteLine("source changed");
if (oldvalue is INotifyCollectionChanged notifyCollectionChanged)
{
notifyCollectionChanged.CollectionChanged += (s, e) =>
{
RenderNodes((IEnumerable<TreeViewNode>)newvalue, _StackLayout, e, null);
};
}
RenderNodes((IEnumerable<TreeViewNode>)newvalue, _StackLayout, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset), null);
}
#endregion
#region Private Static Methods
private static void AddItems(IEnumerable<TreeViewNode> childTreeViewItems, StackLayout parent, TreeViewNode parentTreeViewItem)
{
foreach (var childTreeNode in childTreeViewItems)
{
if (!parent.Children.Contains(childTreeNode))
{
parent.Children.Add(childTreeNode);
}
childTreeNode.ParentTreeViewItem = parentTreeViewItem;
}
}
#endregion
}
}
so in my viewmodel I create my TreeViewNode and bind it to RootNodes property of TreeView. so I added :
private ObservableCollection<TreeViewNode> _treeviewSource;
public ObservableCollection<TreeViewNode> TreeviewSource
{
get => _treeviewSource;
set
{
_treeviewSource = value;
RaisePropertyChanged(() => TreeviewSource);
}
}
But raising property changed while having a big list of TreeViewNode takes waaay too much time. And that is my problem ... I suppose the problem is that it notifies the UI foreach TreeViewNode(stacklayout) in the list and that what it takes time.
I Hope you guys can help with that. Thanks

How can I share the value of a field between back-end C# and a renderer?

My C# looks like this:
public App()
{
InitializeComponent();
MainPage = new Japanese.MainPage();
}
public partial class MainPage : TabbedPage
{
public MainPage()
{
InitializeComponent();
var phrasesPage = new NavigationPage(new PhrasesPage())
{
Title = "Play",
Icon = "ionicons-2-0-1-ios-play-outline-25.png"
};
public partial class PhrasesPage : ContentPage
{
public PhrasesFrame phrasesFrame;
public PhrasesPage()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
App.phrasesPage = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
App.dataChange = true;
phrasesFrame = new PhrasesFrame(this);
phrasesStackLayout.Children.Add(phrasesFrame);
}
public partial class PhrasesFrame : Frame
{
private async Task ShowCard()
{
if (pauseCard == false)
..
and I have an iOS renderer for a tab page
public class TabbedPageRenderer : TabbedRenderer
{
private MainPage _page;
private void OnTabBarReselected(object sender, UITabBarSelectionEventArgs e)
{
...
pauseCard = false;
...
My problem is there is no connection between the two and I would like to know how I can make it so that pauseCard could be set in one place and read in another.
Here is a simple custom Entry example using a bindable bool property that gets changed from the renderer every time the text changes in the entry.
Entry subclass w/ a bindable property called OnOff (bool)
public class CustomPropertyEntry : Entry
{
public static readonly BindableProperty OnOffProperty = BindableProperty.Create(
propertyName: "OnOff",
returnType: typeof(bool),
declaringType: typeof(CustomPropertyEntry),
defaultValue: false);
public bool OnOff
{
get { return (bool)GetValue(OnOffProperty); }
set { SetValue(OnOffProperty, value); }
}
}
iOS Renderer
Note: I keep a reference to the instance of the CustomPropertyEntry passed into OnElementChanged so later I can set its custom property when needed.
public class CustomPropertyEntryRenderer : ViewRenderer<CustomPropertyEntry, UITextField>
{
UITextField textField;
CustomPropertyEntry entry;
protected override void OnElementChanged(ElementChangedEventArgs<CustomPropertyEntry> e)
{
base.OnElementChanged(e);
if (Control == null)
{
textField = new UITextField();
SetNativeControl(textField);
}
if (e.OldElement != null)
{
textField.RemoveTarget(EditChangedHandler, UIControlEvent.EditingChanged);
entry = null;
}
if (e.NewElement != null)
{
textField.AddTarget(EditChangedHandler, UIControlEvent.EditingChanged);
entry = e.NewElement;
}
}
void EditChangedHandler(object sender, EventArgs e)
{
entry.OnOff = !entry.OnOff;
}
}
XAML Example:
<local:CustomPropertyEntry x:Name="customEntry" Text="" />
<Switch BindingContext="{x:Reference customEntry}" IsToggled="{Binding OnOff}" />

How can I implement INotifyPropertyChanged to make Xamarin binding update?I

I have this code:
wordGrid.BindingContext = AS.phrase;
AS.phrase = new PSCViewModel() { English = "abcd" };
AS.phrase.English = "JJJJ";
With the setting of BindingContext on the first line I don't see anything in my view. With it after it works and I see "JJJJ".
Here is my viewModel:
public class PSCViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
int id;
string english;
public PSCViewModel()
{
}
public int Id
{
get { return id; }
set
{
if (value != id)
{
id = value;
onPropertyChanged("ID");
}
}
}
public string English
{
get { return english; }
set
{
if (value != english)
{
english = value;
onPropertyChanged("English");
}
}
}
private void onPropertyChanged(string v)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(v));
}
}
}
Can anyone see why the change to the English field would not cause the new value of JJJJ to be displayed?
this is how I implemented INotifyPropertyChanged.
public class Bindable : INotifyPropertyChanged
{
private Dictionary<string, object> _properties = new Dictionary<string, object>();
/// <summary>
/// Gets the value of a property
        /// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
protected T Get<T>([CallerMemberName] string name = null)
{
object value = null;
if (_properties.TryGetValue(name, out value))
return value == null ? default(T) : (T)value;
return default(T);
}
/// <summary>
/// Sets the value of a property
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="name"></param>
protected void Set<T>(T value, [CallerMemberName] string name = null)
{
if (Equals(value, Get<T>(name)))
return;
_properties[name] = value;
OnPropertyChanged(name);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Here is a sample class describing how to use
public class Transaction : Bindable
{
public Transaction()
{
this.TransactionDate = DateTimeOffset.Now;
this.TransactionType = TransactionType.Add; //enum
this.Quantity = 0;
this.IsDeleted = false;
this.Item = null; //object defined elsewhere
}
public Guid Id { get { return Get<Guid>(); } private set { Set<Guid>(value); } }
public DateTimeOffset? TransactionDate { get { return Get<DateTimeOffset?>(); } set { Set<DateTimeOffset?>(value); } }
public TransactionType TransactionType { get { return Get<TransactionType>(); } set { Set<TransactionType>(value); } }
public double? Quantity { get { return Get<double?>(); } set { Set<double?>(value); } }
public bool? IsDeleted { get { return Get<bool?>(); } set { Set<bool?>(value); } }
public byte[] RowVersion { get { return Get<byte[]>(); } private set { Set<byte[]>(value); } }
public virtual Guid? ItemId { get { return Get<Guid?>(); } set { Set<Guid?>(value); } }
public virtual Item Item { get { return Get<Item>(); } set { Set<Item>(value); } }
}
You probably already found the definition in System.ComponentModel
It's all part of MVVM. Your ViewModel must implement INotifyPropertyChanged. There is only one event in it: PropertyChangedEventHandler PropertyChanged
I usually define a raise method in the ViewModel like this:
protected void RaisePropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Then all the properties in the ViewModel must have they getter/setter like this:
public string AProperty
{
get { return aProperty;}
set
{
if(value != aProperty)
{
aProperty = value;
RaisePropertyChanged("AProperty");
}
}
}
Now, when you bind your View with the ViewModel, it will subscribe to PropertyChanged event an propagate the changes. That's it !
A good start would be to read up on the MVVM pattern and how to implement it in Xamarin Forms. Xamarin has their own tutorials on the topic such as this one:
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/
Basically what you do is create a ViewModel which acts as the BindingContext for the entire page. Within that ViewModel you define properties that are bound to your controls such as Labels, ListViews and TextBoxes. In your case the ViewModel would contain a string property called Phrase that is bound to the control called wordGrid.
public class PhraseViewModel
{
public string Phrase {get; set;}
}
Which can be bound in XAML to e.g. a Label like:
<Label Text="{Binding Phrase}" />

How can I interact with a xamarin forms image in Android?

I have an image in Xamarin Forms. I want to use the native android features to interact with this image. For example, when the image is tapped, I want to know the x,y coordinates of where the image was tapped. I can use Android ImageView but I'm not sure how to cast the Xamarin Forms image to Android ImageView
[assembly: ExportRenderer(typeof(Image), typeof(FloorplanImageRenderer))]
namespace EmployeeApp.Droid.Platform
{
public class FloorplanImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
if (Control == null)
{
var imageView = (ImageView)e.NewElement; // This is not right
}
base.OnElementChanged(e);
}
}
}
But the Control is null....
No, it shouldn't be null. Back to your question, I think first of all, you will need to attach a touch event to the image control in PCL and create a property to hold the coordinate when image get touched. And I think here in your code:
[assembly: ExportRenderer(typeof(Image), typeof(FloorplanImageRenderer))]
I think the Image here should be your custom image control which inherits from Image in PCL.
Create a interface for touch event:
public interface IFloorplanImageController
{
void SendTouched();
}
Create a custom control for image:
public class FloorplanImage : Image, IFloorplanImageController
{
public event EventHandler Touched;
public void SendTouched()
{
Touched?.Invoke(this, EventArgs.Empty);
}
public Tuple<float, float> TouchedCoordinate
{
get { return (Tuple<float, float>)GetValue(TouchedCoordinateProperty); }
set { SetValue(TouchedCoordinateProperty, value); }
}
public static readonly BindableProperty TouchedCoordinateProperty =
BindableProperty.Create(
propertyName: "TouchedCoordinate",
returnType: typeof(Tuple<float, float>),
declaringType: typeof(FloorplanImage),
defaultValue: new Tuple<float, float>(0, 0),
propertyChanged: OnPropertyChanged);
public static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
}
}
Implement the custom renderer:
[assembly: ExportRenderer(typeof(FloorplanImage), typeof(FloorplanImageRenderer))]
namespace EmployeeApp.Droid.Platform
{
public class FloorplanImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
if (Control != null)
{
Control.Clickable = true;
Control.SetOnTouchListener(ImageTouchListener.Instance.Value);
Control.SetTag(Control.Id, new JavaObjectWrapper<FloorplanImage> { Obj = Element as FloorplanImage });
}
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (Control != null)
{
Control.SetOnTouchListener(null);
}
}
base.Dispose(disposing);
}
private class ImageTouchListener : Java.Lang.Object, Android.Views.View.IOnTouchListener
{
public static readonly Lazy<ImageTouchListener> Instance = new Lazy<ImageTouchListener>(
() => new ImageTouchListener());
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
var obj = v.GetTag(v.Id) as JavaObjectWrapper<FloorplanImage>;
var element = obj.Obj;
var controller = element as IFloorplanImageController;
if (e.Action == Android.Views.MotionEventActions.Down)
{
var x = e.GetX();
var y = e.GetY();
element.TouchedCoordinate = new Tuple<float, float>(x, y);
controller?.SendTouched();
}
else if (e.Action == Android.Views.MotionEventActions.Up)
{
}
return false;
}
}
}
public class JavaObjectWrapper<T> : Java.Lang.Object
{
public T Obj { get; set; }
}
}
Use this control like this:
<local:FloorplanImage HeightRequest="300" x:Name="image" WidthRequest="300"
Aspect="AspectFit" Touched="image_Touched" />
code behind:
private void image_Touched(object sender, EventArgs e)
{
var cor = image.TouchedCoordinate;
}

WP7 Developpement : How to make the program wait until the end of an EventHandler?

When my view wants the value of LogoStation, it returns null because my program has not yet executed LoadStation_Completed.
I want my program waits that LoadStation_Completed is executed before continuing.
Thx
public class Infos
{
#region propriétés
private DataServiceCollection<SyndicObject> _infosStation;
public DataServiceCollection<SyndicObject> InfosStation
{
get
{
return _infosStation;
}
set
{
_infosStation = value;
}
}
#endregion
string nameStation;
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
private ImageSource _logoStation;
public ImageSource LogoStation
{
get
{
return _logoStation;
}
set
{
_logoStation = value;
NotifyPropertyChanged("LogoStation");
}
}
public Infos(string station)
{
nameStation = station;
getInfos();
}
public void getInfos()
{
SyndicationContext service = new SyndicationContext(new Uri("http://test/817bee9d-faf4-4680-9d05-e41c2c90ae5a/"));
IQueryable<SyndicObject> requete = (from objectSki in service.Objects
where objectSki.NOMSTATION == nameStation
select objectSki);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
InfosStation = new DataServiceCollection<SyndicObject>();
InfosStation.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(InfoStation_LoadCompleted);
InfosStation.LoadAsync(requete);
}
);
}
void InfoStation_LoadCompleted(object sender, LoadCompletedEventArgs e)
{
LogoStation = new BitmapImage(new Uri(#"http://test/upload/" + InfosStation[0].LOGO, UriKind.Absolute));
}
}
By using the property setter you are using NotifyPropertyChanged (correctly) to tell the UI bound to LogoStation that it has been updated. This should mean that the UI will display nothing initially and then the image when the load has completed.
Without seeing your view code what you have here looks correct - apart from the fact that your Infos class doesn't inherit from INotifyPropertyChanged. This means that the event never gets sent.
Update your class definition and you should be good to go.

Resources