How to optimize xamarin Treeview performence? - xamarin

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

Related

Xamarin Long Press event in all 3 platforms (Android,iOS,UWP) on Stacklayout

I want any Nuget package or any custom component for Long press event which I want to use in listview for all 3 platforms .I have got a solution regarding Android and iOS but didn't find any solution for UWP.
Can anybody help me out of this?
Thanks in advance
!!! Touch can produce a Holding action, but mouse devices generally can't.
https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.uielement.holding?view=winrt-19041
PLC class:
namespace MyNamespace.Effects
{
/// <summary>
/// Эффект длительного нажатия на элемент управления (общая реализация)
/// </summary>
public class LongPressedEffect : RoutingEffect
{
public LongPressedEffect() : base("LongPressedEffect")
{
}
#region LongPressed
public static readonly BindableProperty LongPressedCommandProperty = BindableProperty.CreateAttached("LongPressedCommand", typeof(ICommand),
typeof(LongPressedEffect), null);
public static ICommand GetLongPressedCommand(BindableObject view)
{
return (ICommand)view.GetValue(LongPressedCommandProperty);
}
public static void SetLongPressedCommand(BindableObject view, ICommand value, object parameter)
{
view.SetValue(LongPressedCommandProperty, value);
SetLongPressedCommandParameter(view, parameter);
}
public static void SetLongPressedCommand(BindableObject view, ICommand value)
{
view.SetValue(LongPressedCommandProperty, value);
}
public static readonly BindableProperty LongPressedCommandParameterProperty =
BindableProperty.CreateAttached("LongPressedCommandParameter", typeof(object), typeof(LongPressedEffect), null);
public static object GetLongPressedCommandParameter(BindableObject view)
{
return view.GetValue(LongPressedCommandParameterProperty);
}
public static void SetLongPressedCommandParameter(BindableObject view, object value)
{
view.SetValue(LongPressedCommandParameterProperty, value);
}
#endregion
#region LongPressed
public static readonly BindableProperty ClickCommandProperty = BindableProperty.CreateAttached("ClickCommand", typeof(ICommand),
typeof(LongPressedEffect), null);
public static ICommand GetClickCommand(BindableObject view)
{
return (ICommand)view.GetValue(ClickCommandProperty);
}
public static void SetClickCommand(BindableObject view, ICommand value, object parameter)
{
view.SetValue(ClickCommandProperty, value);
SetClickCommandParameter(view, parameter);
}
public static void SetClickCommand(BindableObject view, ICommand value)
{
view.SetValue(ClickCommandProperty, value);
}
public static readonly BindableProperty ClickCommandParameterProperty =
BindableProperty.CreateAttached("ClickCommandParameter", typeof(object), typeof(LongPressedEffect), null);
public static object GetClickCommandParameter(BindableObject view)
{
return view.GetValue(ClickCommandParameterProperty);
}
public static void SetClickCommandParameter(BindableObject view, object value)
{
view.SetValue(ClickCommandParameterProperty, value);
}
#endregion
}
}
UWP class:
[assembly: ResolutionGroupName("MyNamespace")]
[assembly: ExportEffect(typeof(MyNamespace.UWP.Effects.UWPLongPressedEffect), "LongPressedEffect")]
namespace MyNamespace.UWP.Effects
{
public class UWPLongPressedEffect : PlatformEffect
{
private bool _attached;
public UWPLongPressedEffect()
{
}
/// <summary>
/// Прикрепление обработчика, необходимого для эффекта
/// </summary>
protected override void OnAttached()
{
if (!_attached)
{
if (Control != null)
{
Control.IsHoldingEnabled = true;
Control.Tapped += Tapped;
Control.Holding += Holding;
}
else
{
Container.IsHoldingEnabled = true;
Container.Tapped += Tapped;
Container.Holding += Holding;
}
_attached = true;
}
}
/// <summary>
/// Отсоединение обработчика для эффекта
/// </summary>
protected override void OnDetached()
{
if (_attached)
{
if (Control != null)
{
Control.IsHoldingEnabled = true;
Control.Tapped -= Tapped;
Control.Holding -= Holding;
}
else
{
Container.IsHoldingEnabled = true;
Container.Tapped -= Tapped;
Container.Holding -= Holding;
}
_attached = false;
}
}
private void Tapped(object sender, TappedRoutedEventArgs e)
{
var command = LongPressedEffect.GetClickCommand(Element);
command?.Execute(LongPressedEffect.GetClickCommandParameter(Element));
}
private void Holding(object sender, HoldingRoutedEventArgs e)
{
var command = LongPressedEffect.GetLongPressedCommand(Element);
command?.Execute(LongPressedEffect.GetLongPressedCommandParameter(Element));
}
}
}

How to change custom entry floating label color on button click in Xamarin.Forms

i have created one custom entry field with floating label using custom renderer like from this uri https://github.com/AlejandroRuiz/FloatingTextEntry
i need to change entry underline color as well placeholder color on button click .
Custom Entry Code :
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Xamarin.Forms;
namespace BloodTrace.CustomControls
{
public class XfxEntry : Entry
{
public static readonly BindableProperty ErrorTextProperty = BindableProperty.Create(nameof(ErrorText),
typeof(string),
typeof(XfxEntry),
default(string), propertyChanged: OnErrorTextChangedInternal);
public static readonly BindableProperty FloatingHintEnabledProperty = BindableProperty.Create(nameof(FloatingHintEnabled),
typeof(bool),
typeof(XfxEntry),
true);
public static readonly BindableProperty ActivePlaceholderColorProperty = BindableProperty.Create(nameof(ActivePlaceholderColor),
typeof(Color),
typeof(XfxEntry),
Color.Accent);
/// <summary>
/// ActivePlaceholderColor summary. This is a bindable property.
/// </summary>
public Color ActivePlaceholderColor
{
get { return (Color)GetValue(ActivePlaceholderColorProperty); }
set { SetValue(ActivePlaceholderColorProperty, value); }
}
public Color ErrorPlaceholderColor
{
get { return (Color)GetValue(ErrorPlaceholderColorProperty); }
set { SetValue(ErrorPlaceholderColorProperty, value); }
}
public static readonly BindableProperty ErrorPlaceholderColorProperty = BindableProperty.Create(nameof(ErrorPlaceholderColor),
typeof(Color),
typeof(XfxEntry),
Color.Green);
/// <summary>
/// <c>true</c> to float the hint into a label, otherwise <c>false</c>. This is a bindable property.
/// </summary>
public bool FloatingHintEnabled
{
get { return (bool)GetValue(FloatingHintEnabledProperty); }
set { SetValue(FloatingHintEnabledProperty, value); }
}
/// <summary>
/// Gets or Sets whether or not the Error Style is 'Underline' or 'None'
/// </summary>
public ErrorDisplay ErrorDisplay { get; set; } = ErrorDisplay.None;
public bool IsErrorPlaceholderColorChange { get; set; } = false;
/// <summary>
/// Error text for the entry. An empty string removes the error. This is a bindable property.
/// </summary>
public string ErrorText
{
get { return (string)GetValue(ErrorTextProperty); }
set { SetValue(ErrorTextProperty, value); }
}
/// <summary>
/// Raised when the value of the error text changes
/// </summary>
public event EventHandler<TextChangedEventArgs> ErrorTextChanged;
private static void OnErrorTextChangedInternal(BindableObject bindable, object oldvalue, object newvalue)
{
var materialEntry = (XfxEntry)bindable;
materialEntry.OnErrorTextChanged(bindable, oldvalue, newvalue);
materialEntry.ErrorTextChanged?.Invoke(materialEntry, new TextChangedEventArgs((string)oldvalue, (string)newvalue));
}
protected virtual void OnErrorTextChanged(BindableObject bindable, object oldvalue, object newvalue) { }
}
public enum ErrorDisplay
{
Underline,
None
}
}
Android Renderer : -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using BloodTrace;
using BloodTrace.Droid.Renderer;
using Application = Android.App.Application;
using Color = Xamarin.Forms.Color;
using AColor = Android.Graphics.Color;
using FormsAppCompat = Xamarin.Forms.Platform.Android.AppCompat;
using BloodTrace.CustomControls;
using Android.Support.Design.Widget;
using Android.Text;
using Xamarin.Forms;
using Android.Content.Res;
using Android.Views.InputMethods;
using Java.Lang;
using Android.Support.V7.Widget;
using Android.Util;
using Xamarin.Forms.Platform.Android;
using System.ComponentModel;
using Android.Text.Method;
using Android.Support.V4.View;
using BloodTrace.Droid.Extensions;
using BloodTraceSharedProject.Extensions;
[assembly: ExportRenderer(typeof(XfxEntry), typeof(XfxEntryRendererDroid))]
namespace BloodTrace.Droid.Renderer
{
public class XfxEntryRendererDroid : FormsAppCompat.ViewRenderer<XfxEntry, TextInputLayout>,
ITextWatcher,
TextView.IOnEditorActionListener
{
private bool _hasFocus;
private ColorStateList _defaultTextColor;
public XfxEntryRendererDroid(Context context) : base(context)
{
AutoPackage = false;
}
protected EditText EditText => Control.EditText;
public bool OnEditorAction(TextView v, ImeAction actionId, KeyEvent e)
{
if ((actionId == ImeAction.Done) || ((actionId == ImeAction.ImeNull) && (e.KeyCode == Keycode.Enter)))
{
Control.ClearFocus();
HideKeyboard();
((IEntryController)Element).SendCompleted();
}
return true;
}
public virtual void AfterTextChanged(IEditable s)
{
}
public virtual void BeforeTextChanged(ICharSequence s, int start, int count, int after)
{
}
public virtual void OnTextChanged(ICharSequence s, int start, int before, int count)
{
if (string.IsNullOrWhiteSpace(Element.Text) && (s.Length() == 0)) return;
((IElementController)Element).SetValueFromRenderer(Entry.TextProperty, s.ToString());
}
protected override TextInputLayout CreateNativeControl()
{
var textInputLayout = new TextInputLayout(Context);
var editText = new AppCompatEditText(Context)
{
SupportBackgroundTintList = ColorStateList.ValueOf(GetPlaceholderColor())
};
editText.SetTextSize(ComplexUnitType.Sp, (float)Element.FontSize);
textInputLayout.AddView(editText);
return textInputLayout;
}
protected override void OnElementChanged(ElementChangedEventArgs<XfxEntry> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
if (Control != null)
EditText.FocusChange -= ControlOnFocusChange;
if (e.NewElement != null)
{
var ctrl = CreateNativeControl();
SetNativeControl(ctrl);
if (!string.IsNullOrWhiteSpace(Element.AutomationId))
EditText.ContentDescription = Element.AutomationId;
_defaultTextColor = EditText.TextColors;
Focusable = true;
EditText.ShowSoftInputOnFocus = true;
// Subscribe
EditText.FocusChange += ControlOnFocusChange;
EditText.AddTextChangedListener(this);
EditText.SetOnEditorActionListener(this);
EditText.ImeOptions = ImeAction.Done;
SetText();
SetHintText();
SetErrorText();
SetFontAttributesSizeAndFamily();
SetInputType();
SetTextColor();
SetHorizontalTextAlignment();
SetFloatingHintEnabled();
SetIsEnabled();
SetErrorDisplay();
SetLabelAndUnderlineColor();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Entry.PlaceholderProperty.PropertyName)
SetHintText();
else if (e.PropertyName == XfxEntry.ErrorTextProperty.PropertyName)
SetErrorText();
else if (e.PropertyName == Entry.IsPasswordProperty.PropertyName ||
e.PropertyName == InputView.KeyboardProperty.PropertyName)
SetInputType();
else if (e.PropertyName == Entry.TextProperty.PropertyName)
SetText();
else if (e.PropertyName == Entry.HorizontalTextAlignmentProperty.PropertyName)
SetHorizontalTextAlignment();
else if (e.PropertyName == XfxEntry.FloatingHintEnabledProperty.PropertyName)
SetFloatingHintEnabled();
else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
SetIsEnabled();
else if ((e.PropertyName == Entry.FontAttributesProperty.PropertyName) ||
(e.PropertyName == Entry.FontFamilyProperty.PropertyName) ||
(e.PropertyName == Entry.FontSizeProperty.PropertyName))
SetFontAttributesSizeAndFamily();
else if (e.PropertyName == XfxEntry.ActivePlaceholderColorProperty.PropertyName ||
e.PropertyName == Entry.PlaceholderColorProperty.PropertyName)
SetLabelAndUnderlineColor();
else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
SetTextColor();
}
private void ControlOnFocusChange(object sender, FocusChangeEventArgs args)
{
_hasFocus = args.HasFocus;
if (_hasFocus)
{
var manager = (InputMethodManager)Application.Context.GetSystemService(Context.InputMethodService);
EditText.PostDelayed(() =>
{
EditText.RequestFocus();
manager.ShowSoftInput(EditText, 0);
},
100);
}
var isFocusedPropertyKey = Element.GetInternalField<BindablePropertyKey>("IsFocusedPropertyKey");
((IElementController)Element).SetValueFromRenderer(isFocusedPropertyKey, _hasFocus);
//This will change floating label entry SetUnderlineColor >>>> SetUnderlineColor color basedon focus or non focus
SetUnderlineColor(_hasFocus ? GetActivePlaceholderColor() : GetPlaceholderColor());
}
protected AColor GetPlaceholderColor() => Element.PlaceholderColor.ToAndroid(Color.FromHex("#80000000"));
private AColor GetActivePlaceholderColor() => Element.ActivePlaceholderColor.ToAndroid(global::Android.Resource.Attribute.ColorAccent, Context);
protected virtual void SetLabelAndUnderlineColor()
{
var defaultColor = GetPlaceholderColor();
var activeColor = GetActivePlaceholderColor();
SetHintLabelDefaultColor(defaultColor);
SetHintLabelActiveColor(activeColor);
SetUnderlineColor(_hasFocus ? activeColor : defaultColor);
}
//This will change entry under line color
private void SetUnderlineColor(AColor color)
{
var element = (ITintableBackgroundView)EditText;
element.SupportBackgroundTintList = ColorStateList.ValueOf(color);
}
private void SetHintLabelActiveColor(AColor color)
{
var hintText = Control.Class.GetDeclaredField("mFocusedTextColor");
hintText.Accessible = true;
hintText.Set(Control, new ColorStateList(new int[][] { new[] { 0 } }, new int[] { color }));
}
private void SetHintLabelDefaultColor(AColor color)
{
var hint = Control.Class.GetDeclaredField("mDefaultTextColor");
hint.Accessible = true;
hint.Set(Control, new ColorStateList(new int[][] { new[] { 0 } }, new int[] { color }));
}
private void SetText()
{
if (EditText.Text != Element.Text)
{
EditText.Text = Element.Text;
if (EditText.IsFocused)
EditText.SetSelection(EditText.Text.Length);
}
}
private void SetHintText()
{
Control.Hint = Element.Placeholder;
}
private void SetTextColor()
{
if (Element.TextColor == Color.Default)
{
EditText.SetTextColor(_defaultTextColor);
}
else
{
EditText.SetTextColor(Element.TextColor.ToAndroid());
}
}
private void SetHorizontalTextAlignment()
{
switch (Element.HorizontalTextAlignment)
{
case Xamarin.Forms.TextAlignment.Center:
EditText.Gravity = GravityFlags.CenterHorizontal;
break;
case Xamarin.Forms.TextAlignment.End:
EditText.Gravity = GravityFlags.Right;
break;
default:
EditText.Gravity = GravityFlags.Left;
break;
}
}
public void SetFloatingHintEnabled()
{
Control.HintEnabled = Element.FloatingHintEnabled;
Control.HintAnimationEnabled = Element.FloatingHintEnabled;
SetFontAttributesSizeAndFamily();
}
public void SetErrorDisplay()
{
switch (Element.ErrorDisplay)
{
case ErrorDisplay.None:
Control.ErrorEnabled = false;
break;
case ErrorDisplay.Underline:
Control.ErrorEnabled = true;
break;
}
}
protected void HideKeyboard()
{
var manager = (InputMethodManager)Application.Context.GetSystemService(Context.InputMethodService);
manager.HideSoftInputFromWindow(EditText.WindowToken, 0);
}
//here we can set entry floating label >>> input text size
private void SetFontAttributesSizeAndFamily()
{
EditText.Typeface = Control.Typeface = Element.ToTypeface();
EditText.SetTextSize(ComplexUnitType.Sp, (float)Element.FontSize);
//var a = (float)Element.FontSize; //18
}
private void SetErrorText()
{
Control.Error = !string.IsNullOrEmpty(Element.ErrorText) ? Element.ErrorText : null;
}
private void SetIsEnabled()
{
EditText.Enabled = Element.IsEnabled;
}
private void SetInputType()
{
EditText.InputType = Element.Keyboard.ToInputType();
if (Element.IsPassword && (EditText.InputType & InputTypes.ClassText) == InputTypes.ClassText)
{
EditText.TransformationMethod = new PasswordTransformationMethod();
EditText.InputType = EditText.InputType | InputTypes.TextVariationPassword;
}
if (Element.IsPassword && (EditText.InputType & InputTypes.ClassNumber) == InputTypes.ClassNumber)
{
EditText.TransformationMethod = new PasswordTransformationMethod();
EditText.InputType = EditText.InputType | InputTypes.NumberVariationPassword;
}
}
}
}
XML : -
<xfx:XfxEntry x:Name="EntPassword" Placeholder="Password" IsPassword="True" Focused="EntPassword_Focused"></xfx:XfxEntry>
<Button Text="Sign Up" BackgroundColor="#C62729" TextColor="White" x:Name="BtnSignUp" Clicked="BtnSignUp_Clicked" ></Button>
Please help.
Thanks in advance

Xamarin, Both ListView and RecyclerView, click one item, another one selected

I'm having a trouble with both ListView and RecyclerView
Initially, I created a ListView, everything is fine. Then I set onClick event for it so that every time I click an item, it changes its color to yellow. The OnClick function I wrote in the MainActivity. Problem is that when I test, not only that item changes its color but 2 items change. I read that it's because I reuse the view.
So I switch my tactics, using RecyclerView instead but same problem occurs. When I click one item to change its color, another below also changes. I guess it's because both ListView and RecyclerView reuse those Item so they confuse when I click one.
I don't know how to solve this problem, I found a solution is to add an array of boolean which marks which item is clicked but it doesn't work. Any idea guys?
So here is the code
MainActivity
class MainActivity : Activity
{
public RecyclerView recyclerView;
public RecyclerView.LayoutManager manager;
public RecyclerView.Adapter adapter;
List<Row> lst;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
init();
recyclerView = (RecyclerView)FindViewById(Resource.Id.recyclerView);
manager = new LinearLayoutManager(this);
recyclerView.SetLayoutManager(manager);
CustomAdapter adapter = new CustomAdapter(lst, this);
adapter.ItemClick += onItemClick;
recyclerView.SetAdapter(adapter);
}
public void init()
{
lst = new List<Row>();
for (int i = 0; i < 15; i++)
{
Row row = new Row() { field1="1:43:00", field2="09-Apr-16", field3="KPI/Overflow", field4="Kevin Bacon", field5="Unowned", field6= "People Counting # IPCAM-ID-C-1-1" };
lst.Add(row);
}
}
public void onItemClick(object sender, int position)
{
int itemPos = position + 1;
//Toast.MakeText(this, "this is " + itemPos, ToastLength.Short).Show();
recyclerView.GetChildAt(position).SetBackgroundColor(Android.Graphics.Color.Green);
}
}
Custom adapter
public class CustomAdapter : RecyclerView.Adapter
{
public Activity _activity;
public List<Row> lst;
public event EventHandler<int> ItemClick;
public CustomAdapter(List<Row> lst, Activity activity)
{
this.lst = lst;
this._activity = activity;
}
public override int ItemCount
{
get
{
return lst.Count;
}
}
public void OnClick(int position)
{
if (ItemClick!=null)
{
ItemClick(this, position);
}
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
MyViewHolder myholder = holder as MyViewHolder;
myholder.textView1.Text = lst[position].field1;
myholder.textView2.Text = lst[position].field2;
myholder.textView3.Text = lst[position].field3;
myholder.textView4.Text = lst[position].field4;
myholder.textView5.Text = lst[position].field5;
myholder.textView6.Text = lst[position].field6;
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View v = this._activity.LayoutInflater.Inflate(Resource.Layout.item, parent, false);
TextView tv1 = (TextView)v.FindViewById(Resource.Id.textView1);
TextView tv2 = (TextView)v.FindViewById(Resource.Id.textView2);
TextView tv3 = (TextView)v.FindViewById(Resource.Id.textView3);
TextView tv4 = (TextView)v.FindViewById(Resource.Id.textView4);
TextView tv5 = (TextView)v.FindViewById(Resource.Id.textView5);
TextView tv6 = (TextView)v.FindViewById(Resource.Id.textView6);
MyViewHolder holder = new MyViewHolder(v, OnClick) { textView1 = tv1, textView2 = tv2, textView3 = tv3, textView4 = tv4, textView5 = tv5, textView6 = tv6 };
return holder;
}
}
class MyViewHolder : RecyclerView.ViewHolder
{
public TextView textView1, textView2, textView3, textView4, textView5, textView6;
public View mainView;
public MyViewHolder(View view, Action<int> listener) : base(view)
{
mainView = view;
mainView.Click += (sender, e) => listener(base.Position);
}
}
I followed the example for the OnClick handler on Xamarin site
https://developer.xamarin.com/guides/android/user_interface/recyclerview/
Your issue is with your code. You send the correct position to your event handler, but then you increment it by one in the Activity. Both ends should be using the 0-based index of the item position. There is no need to increment by one.
For changing the background color of the selected item, you can use a selector in XML so you wouldn't even need to do this in code.
Here is an example.
row_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="#android:color/green" />
<item android:state_selected="false" android:color="#android:color/transparent"/>
</selector>
row_content.axml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/row_layout_parent"
android:background="#drawable/row_selector">
<!-- your row content -->
</LinearLayout>
Then your view holder would be updated to this...
class MyViewHolder : RecyclerView.ViewHolder
{
public TextView textView1, textView2, textView3, textView4, textView5, textView6;
public View mainView;
private LinearLayout _layoutParent;
public MyViewHolder(View view, Action<int> listener) : base(view)
{
mainView = view;
_layoutParent = mainView.FindViewById<LinearLayout>(Resource.Id.row_layout_parent);
_layoutParent.Click += (sender, e) => _layoutParent.Selected = true;
}
}
I removed the other click event. If you still need it for other reasons, then you can add it back, but it's not necessary for just setting the item background color when selected.
For Listview you should set choiceMode as below.
listView.ChoiceMode = ChoiceMode.Single;
Hope it help you :)-
Create a reusable recycleview adapter GENERIC
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Support.V7.App;
using Android.Support.V7.Widget;
using Android.Text;
using Android.Text.Style;
using Android.Util;
using Android.Views;
using Android.Widget;
using Java.Util.Zip;
using ActionMenuView = Android.Support.V7.Widget.ActionMenuView;
namespace Android.Basic.Core
{
public class GenericRecyclerViewAdapter<T> : RecyclerView.Adapter
{
/// <summary>
/// You can set this for different custom cardview
/// </summary>
private int CardViewResourceLayout { get; set; }
public ObservableCollection<T> Items { get; private set; }
public event EventHandler<RecyclerViewViewHolder> ItemViewTemplated;
public RecyclerView.LayoutManager layoutManager;
public GenericRecyclerViewAdapter(RecyclerView recyclerView, IEnumerable<T> items, int cardViewResourceLayout, bool isList = true, bool isVertical = true) : base()
{
if(isList)
{
var vertical = isVertical ? LinearLayoutManager.Vertical : LinearLayoutManager.Horizontal;
layoutManager = new LinearLayoutManager(recyclerView.Context, vertical, false);
}
else
{
var vertical = isVertical ? GridLayoutManager.Vertical : GridLayoutManager.Horizontal;
layoutManager = new GridLayoutManager(recyclerView.Context, 3, vertical, false);
}
recyclerView.SetLayoutManager(layoutManager);
this.Items = new ObservableCollection<T>(items);
this.CardViewResourceLayout = cardViewResourceLayout;
this.Items.CollectionChanged += delegate
{
this.NotifyDataSetChanged();
};
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
var itemView = LayoutInflater.From(parent.Context).Inflate(CardViewResourceLayout, parent, false);
#if DEBUG
Log.Info("GenericRecyclerViewAdapter - ", CardViewResourceLayout.ToString());
#endif
RecyclerViewViewHolder vh = new RecyclerViewViewHolder(itemView);
return vh;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
RecyclerViewViewHolder vh = holder as RecyclerViewViewHolder;
vh.ItemPosition = position;
vh.TemplateView.Tag = position;
vh.TemplateView.Click -= TemplateView_Click;
vh.TemplateView.Click += TemplateView_Click;
ItemViewTemplated?.Invoke(this, vh);
}
public event EventHandler<T> ItemClicked;
private void TemplateView_Click(object sender, EventArgs e)
{
var position = (int)((View)sender).Tag;
this.ItemClicked?.Invoke(sender, this.Items[position]);
}
public override int ItemCount
{
get { return this.Items.Count; }
}
public override long GetItemId(int position)
{
return base.GetItemId(position);
}
}
public class RecyclerViewViewHolder : RecyclerView.ViewHolder, View.IOnCreateContextMenuListener,
IMenuItemOnMenuItemClickListener
{
public View TemplateView { get; private set; }
public int ItemPosition { get; set; }
public event EventHandler<MenuInfo> ContextMenuCreated;
public event EventHandler<object> MenuItemClicked;
public MenuInfo MenuInfo { get; private set; }
public object Data { get; set; }
public RecyclerViewViewHolder(View itemView) : base(itemView)
{
// Locate and cache view references:
this.TemplateView = itemView;
this.TemplateView.SetOnCreateContextMenuListener(this);
}
public void OnCreateContextMenu(IContextMenu menu, View v, IContextMenuContextMenuInfo menuInfo)
{
MenuInfo = new MenuInfo(menu, v, menuInfo);
ContextMenuCreated?.Invoke(this, MenuInfo);
}
private Android.Views.MenuInflater menuInflater = null;
/// <summary>
/// After ContextMenuCreated
/// </summary>
/// <param name="resourcemenu"></param>
public void InflateMenu(int resourcemenu, SpannableString titleColor = null, object dta = null)
{
if (dta != null)
this.Data = dta;
if (this.TemplateView.Context is AppCompatActivity activity)
{
menuInflater = activity.MenuInflater;
}
else if (this.TemplateView.Context is Activity activity2)
{
menuInflater = activity2.MenuInflater;
}
var contextMenu = this.MenuInfo.ContextMenu;
contextMenu.Clear();
menuInflater.Inflate(resourcemenu, contextMenu);
var num = contextMenu.Size() - 1;
for (int i = 0; i <= num; i++)
{
var men = contextMenu.GetItem(i);
if(titleColor != null)
{
if (i == 0)
{
men.SetTitle(titleColor);
men.SetChecked(true);
}
}
if (i != 0)
{
men.SetOnMenuItemClickListener(this);
}
}
}
public bool OnMenuItemClick(IMenuItem item)
{
this.MenuItemClicked?.Invoke(item, this.Data);
return true;
}
public float PosX;
public float PosY;
}
public class MenuInfo
{
public IContextMenu ContextMenu { get; }
public View View { get; }
public IContextMenuContextMenuInfo ContextMenuInfo { get; }
public MenuInfo(IContextMenu contextMenu, View view, IContextMenuContextMenuInfo menuInfo)
{
this.ContextMenu = contextMenu;
this.View = view;
this.ContextMenuInfo = menuInfo;
}
}
}
Usage
RecyclerView recyclerView = new RecyclerView(this);
var viewAdapter = new Android.Basic.Core.GenericRecyclerViewAdapter<Java.IO.File>(recyclerView, files, Resource.Layout.directory_item);
var indiColor = ThemeHelper.IsDark ? ColorHelper.GetRandomLightColor() : ColorHelper.GetRandomDarkColor();
viewAdapter.ItemViewTemplated += (dd, holder) =>
{
var file = files[holder.ItemPosition];
var view = holder.ItemView;
var expanded = view.FindViewById<ExpandedView>(Resource.Id.expandedView);
expanded.SetToggleColor(indiColor);
expanded.SetTitle(file.Name);
GenerateRecycler(expanded, file);
};
recyclerView.SetAdapter(viewAdapter);
expandedView.AddExpandedView(recyclerView);

How to create checkbox in xamarin forms

I need a checkbox control in xamarin forms, and also the event when the control is check, how can I get this, I am using switch control, But its not having any event when IsChecked as changed, I need to group the checkbox, its not possible in switch control in xamarin forms
The Switch control has a Toggled event that will fire when the state is changed.
Forms does not have a Checkbox control because there is no underlying Checkbox control in each mobile platform for it to map to.
XF Labs (an open source set of extensions for XF) does have a beta checkbox control that may fit your needs.
public class CustomCheckbox : Image
{
private const string CheckboxUnCheckedImage = "checkbox_unchecked";
private const string CheckboxCheckedImage = "checkbox_checked";
public CustomCheckbox()
{
Source = CheckboxUnCheckedImage;
var imageTapGesture = new TapGestureRecognizer();
imageTapGesture.Tapped += ImageTapGestureOnTapped;
GestureRecognizers.Add(imageTapGesture);
PropertyChanged += OnPropertyChanged;
}
private void ImageTapGestureOnTapped(object sender, EventArgs eventArgs)
{
if (IsEnabled)
{
Checked = !Checked;
}
}
/// <summary>
/// The checked changed event.
/// </summary>
public event EventHandler<bool> CheckedChanged;
/// <summary>
/// The checked state property.
/// </summary>
public static readonly BindableProperty CheckedProperty = BindableProperty.Create("Checked", typeof(bool), typeof(CustomCheckbox), false, BindingMode.TwoWay, propertyChanged: OnCheckedPropertyChanged);
public bool Checked
{
get
{
return (bool)GetValue(CheckedProperty);
}
set
{
if (Checked != value)
{
SetValue(CheckedProperty, value);
CheckedChanged?.Invoke(this, value);
}
}
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e?.PropertyName == IsEnabledProperty.PropertyName)
{
Opacity = IsEnabled ? 1 : 0.5;
}
}
private static void OnCheckedPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var checkBox = bindable as CustomCheckbox;
if (checkBox != null)
{
var value = newValue as bool?;
checkBox.Checked = value.GetValueOrDefault();
checkBox.Source = value.GetValueOrDefault() ? CheckboxCheckedImage : CheckboxUnCheckedImage;
}
}
}

Prism2 Region Adapter for AvalonDock's DocumentPane and DockingPane?

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;
}

Resources