Custome StackPanel Prism RegionAdapter для поддержки заказов
У меня есть следующая реализация RegionAdapter для StackPanel, но мне нужен строгий порядок элементов, которые я связываю с регионом, может кто-нибудь помочь?
Я хочу, чтобы представления, которые регистрируют себя в регионе, могли иметь возможность контролировать их положение, может быть, какой-то индекс
protected override void Adapt(IRegion region, StackPanel regionTarget)
{
region.Views.CollectionChanged += (sender, e) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
regionTarget.Children.Add(element);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (UIElement elementLoopVariable in e.OldItems)
{
var element = elementLoopVariable;
if (regionTarget.Children.Contains(element))
{
regionTarget.Children.Remove(element);
}
}
break;
}
};
}
3 ответа
Способ решения этой проблемы во многом зависит от того, относится ли сортировка к (а) типу представления или (б) к экземпляру представления. Первый будет иметь место, если вы хотите указать это, например, представления типа ViewA
должно быть выше представления типа ViewB
, Последнее имеет место, если вы хотите указать, как сортируются несколько конкретных экземпляров одного и того же типа представления.
A. сортировать по типу
Опция заключается в реализации пользовательского атрибута, что-то вроде OrderIndexAttribute
, который предоставляет целочисленное свойство:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public OrderIndexAttribute : Attribute
{
public int Index { get; }
public OrderIndexAttribute(int index)
{
Index = index;
}
}
Пометьте свой класс представления этим атрибутом:
[OrderIndex(2)]
public ViewA : UserControl
{...}
Получите атрибут типа при добавлении представления в регион:
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
// Get index for view
var viewType = element.GetType();
var viewIndex= viewType.GetCustomAttribute<OrderIndexAttribute>().Index;
// This method needs to iterate through the views in the region and determine
// where a view with the specified index needs to be inserted
var insertionIndex = GetInsertionIndex(viewIndex);
regionTarget.Children.Insert(insertionIndex, element);
}
break;
Б. Сортировать пример мудрым
Сделайте так, чтобы ваши представления реализовали интерфейс:
public interface ISortedView
{
int Index { get; }
}
При добавлении представления в регион попробуйте привести вставленное представление к интерфейсу, прочитать индекс, а затем сделать то же, что и выше:
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
// Get index for view
var sortedView = element as ISortedView;
if (sortedView != null)
{
var viewIndex = sortedView.Index;
// This method needs to iterate through the views in the region and determine
// where a view with the specified index needs to be inserted
var insertionIndex = GetInsertionIndex(viewIndex);
regionTarget.Children.Insert(insertionIndex, sortedView);
}
else
{ // Add at the end of the StackPanel or reject adding the view to the region }
}
Ответ от Марка и Р. Эванса помог мне создать мой собственный, более универсальный RegionAdapter со следующими улучшениями:
- использует ViewSortHint для совместимости с Prism 6
- Совместимость с Prism 7 / .Net 5
- Вспомогательный класс для использования в нескольких адаптерах
- меньше кода
Метод адаптации:
protected override void Adapt(IRegion region, StackPanel regionTarget)
{
region.Views.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
foreach (FrameworkElement item in e.NewItems)
{
regionTarget.Children.Insert(regionTarget.Children.GetInsertionIndex(item), item);
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
foreach (UIElement item in e.OldItems)
{
if (regionTarget.Children.Contains(item))
{
regionTarget.Children.Remove(item);
}
}
};
}
Помощник / расширение:
internal static int GetInsertionIndex(this IList items, in object newItem)
{
// Return the list index of the viewIndex
foreach (object item in items)
{
var currentIndex = item.GetType().GetCustomAttribute<ViewSortHintAttribute>()?.Hint ?? "0";
var intendedIndex = newItem.GetType().GetCustomAttribute<ViewSortHintAttribute>()?.Hint ?? "0";
if (currentIndex.CompareTo(intendedIndex) >= 0)
return items.IndexOf(item);
}
// if no greater index is found, insert the item at the end
return items.Count;
}
Я обнаружил, что случай Марка "Сортировка по типу A." очень помог в моей ситуации. Мне нужно было отсортировать представления по региону с помощью атрибута OrderIndexAttribute и по-прежнему иметь возможность добавлять представление, если оно фактически не имело атрибута OrderIndexAttribute.
Как вы увидите ниже, я сделал это, отслеживая индексы представлений в списке. Индекс вставки представления без атрибута по умолчанию равен нулю, так что он сортируется в начало (или верх) StackPanel.
Этот исходный пост довольно старый, но, возможно, кто-то наткнется на него, как и я, и сочтет мой вклад полезным. Предложения по рефакторингу приветствуются.:-)
public class StackPanelRegionAdapter : RegionAdapterBase<StackPanel>
{
private readonly List<int> _indexList;
public StackPanelRegionAdapter(IRegionBehaviorFactory behaviorFactory)
: base(behaviorFactory)
{
_indexList = new List<int>();
}
protected override void Adapt(IRegion region, StackPanel regionTarget)
{
region.Views.CollectionChanged += (sender, e) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
var viewType = element.GetType();
// Get the custom attributes for this view, if any
var customAttributes = viewType.GetCustomAttributes(false);
var viewIndex = 0; // Default the viewIndex to zero
// Determine if the view has the OrderIndexAttribute.
// If it does have the OrderIndexAttribute, get its sort index.
if (HasThisAttribute(customAttributes))
{
viewIndex= viewType.GetCustomAttribute<OrderIndexAttribute>().Index;
}
// Get the insertion index
var insertionIndex = GetInsertionIndex(viewIndex);
regionTarget.Children.Insert(insertionIndex, element);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (UIElement elementLoopVariable in e.OldItems)
{
var element = elementLoopVariable;
if (regionTarget.Children.Contains(element))
{
regionTarget.Children.Remove(element);
}
}
break;
}
};
}
private static bool HasThisAttribute(IReadOnlyList<object> customAttributes)
{
// Determine if the view has the OrderIndexAttribute
if (customAttributes.Count == 0) return false;
for (var i = 0; i < customAttributes.Count; i++)
{
var name = customAttributes[i].GetType().Name;
if (name == "OrderIndexAttribute") return true;
}
return false;
}
private int GetInsertionIndex(in int viewIndex)
{
// Add the viewIndex to the index list if not already there
if (_indexList.Contains(viewIndex) == false)
{
_indexList.Add(viewIndex);
_indexList.Sort();
}
// Return the list index of the viewIndex
for (var i = 0; i < _indexList.Count; i++)
{
if (_indexList[i].Equals(viewIndex))
{
return i;
}
}
return 0;
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
}