Как создать собственный MultiSelector/ItemsControl в WPF/C#
-
22-07-2019 - |
Вопрос
Я пытаюсь создать приложение для построения диаграмм на C#/WPF.То, что я собираюсь сделать, чем-то похоже на Microsoft Visio, хотя я не пытаюсь его клонировать.Я как бы написал этот вопрос во время написания кода и просто изложил в нем все проблемы, которые у меня возникли, на случай, если кто-то найдет это полезным.Возможно, я слишком много думал, но мне кажется, что я мог бы блевать на клавиатуре и писать код получше, поэтому не стесняйтесь давать любые предложения по каждой уловленной вами детали (грамматика исключена :-))
Суммируя:
Почему все элементы расположены в (0,0)?
Код:
public class Diagram : MultiSelector
{
public Diagram()
{
this.CanSelectMultipleItems = true;
// The canvas supports absolute positioning
FrameworkElementFactory panel = new FrameworkElementFactory(typeof(Canvas));
this.ItemsPanel = new ItemsPanelTemplate(panel);
// Tells the container where to position the items
this.ItemContainerStyle = new Style();
this.ItemContainerStyle.Setters.Add(new Setter(Canvas.LeftProperty, new Binding("X")));
this.ItemContainerStyle.Setters.Add(new Setter(Canvas.TopProperty, new Binding("Y")));
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
FrameworkElement contentitem = element as FrameworkElement;
Binding leftBinding = new Binding("X");
Binding topBinding = new Binding("Y");
contentitem.SetBinding(Canvas.LeftProperty, leftBinding);
contentitem.SetBinding(Canvas.TopProperty, topBinding);
base.PrepareContainerForItemOverride(element, item);
}
public class DiagramItem : ContentControl
{
private Point _location;
public DiagramItem()
{
}
static DiagramItem()
{
}
public Point Location
{
get { return _location; }
set
{
_location = value;
}
}
public double X
{
get { return _location.X; }
set
{
_location.X = value;
}
}
public double Y
{
get { return _location.Y; }
set
{
_location.Y = value;
}
}
}
//...
Итак, идея в том, что Диаграмма:Элементы управления помещает свой элемент на панель Canvas в позицию, определенную в Item DiagramItem.Location.IOW, когда я меняю свойство X в DiagramItem, Diagram перемещает элемент по оси X.
Примечание: MultiSelector является производным от ItemsControl и Selector и используется здесь только потому, что мне нужно, чтобы отображаемый элемент можно было выбрать.
Обратите внимание, что я бы предпочел не использовать xaml, если это возможно.
В долгосрочном плане:
Экземпляр диаграммы, видимый пользователю, имеет следующие требования:
- Имеет несколько элементов DiagramItems.
- Пользователь может выбрать несколько элементов DiagramItems.
- Размер элементов DiagramItem можно изменять, поворачивать и перетаскивать в любое место диаграммы.
- Можно перемещаться между элементами DiagramItem с помощью клавиатуры.
По сути, у меня есть два, а возможно, и три класса, имеющих отношение к этому вопросу.
- Диаграмма расширяет System.Windows.Controls.Primitives.Мультиселектор :Селектор:Элементы управления
- Элемент диаграммы простирается КонтентКонтроль или какой-то другой контроль
Diagram.ItemsPanel, также известная как визуальная панель, отображающая элементы, должна быть панелью, поддерживающей абсолютное позиционирование, например Холст.
Как мне реализовать класс, производный от MultiSelector, и какие ресурсы вы можете указать, имеющие отношение к этому вопросу?
Что нужно учитывать при реализации пользовательского MultiSelector/ItemsControl?
Ресурсы:
Я нашел очень мало ресурсов, имеющих отношение к моей проблеме, но опять же, я не уверен, что мне следует искать.Я прочитал исходный код ListBox и ListBoxItem с помощью Reflector, но не нашел его очень полезным.
Другие источники:
- System.Windows.Controls.Primitives.MultiSelector
- System.Windows.Controls.ItemsControl
- ItemsControl.ItemsPanel
- System.Windows.Controls.Canvas
- Расположение элементов, когда Canvas является ItemsPanel элемента управления ItemsControl.
- Использование шаблонов для настройки элементов управления WPF
- Создание элемента управления элементами
Решение
Хорошо, очевидно, этого можно легко добиться, используя привязки и структуру свойств.
public class Diagram : MultiSelector
{
public Diagram()
{
this.CanSelectMultipleItems = true;
// The canvas supports absolute positioning
FrameworkElementFactory panel = new FrameworkElementFactory(typeof(Canvas));
this.ItemsPanel = new ItemsPanelTemplate(panel);
// Tells the container where to position the items
this.ItemContainerStyle = new Style();
this.ItemContainerStyle.Setters.Add(new Setter(Canvas.LeftProperty, new Binding("X")));
this.ItemContainerStyle.Setters.Add(new Setter(Canvas.TopProperty, new Binding("Y")));
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
FrameworkElement contentitem = element as FrameworkElement;
Binding leftBinding = new Binding("XProperty");
leftBinding.Source = contentitem;
Binding topBinding = new Binding("YProperty");
topBinding.Source = contentitem;
contentitem.SetBinding(Canvas.LeftProperty, leftBinding);
contentitem.SetBinding(Canvas.TopProperty, topBinding);
base.PrepareContainerForItemOverride(element, item);
}
public class DiagramItem : ContentControl
{
public static readonly DependencyProperty XProperty;
public static readonly DependencyProperty YProperty;
public static readonly RoutedEvent SelectedEvent;
public static readonly RoutedEvent UnselectedEvent;
public static readonly DependencyProperty IsSelectedProperty;
public DiagramItem()
{
}
static DiagramItem()
{
XProperty = DependencyProperty.Register("XProperty", typeof(Double), typeof(DiagramItem));
YProperty = DependencyProperty.Register("YProperty", typeof(Double), typeof(DiagramItem));
SelectedEvent = MultiSelector.SelectedEvent.AddOwner(typeof(DiagramItem));
UnselectedEvent = MultiSelector.SelectedEvent.AddOwner(typeof(DiagramItem));
IsSelectedProperty = MultiSelector.IsSelectedProperty.AddOwner(typeof(DiagramItem));
}
public Double X
{
get
{
return (Double)this.GetValue(XProperty);
}
set
{
this.SetValue(XProperty, value);
}
}
public Double Y
{
get
{
return (Double)this.GetValue(YProperty);
}
set
{
this.SetValue(YProperty, value);
}
}
public Point Location
{
get
{
return new Point(X, Y);
}
set
{
this.X = value.X;
this.Y = value.Y;
}
}
}
Волшебство заключается в правильном использовании Bingings, ключом было добавить элемент контента в качестве источника.Следующим шагом, очевидно, будет обработка выбора элементов, но это уже другой вопрос.
Другие советы
Если это вам поможет, я написал статью о проекте кода на основе моего пользовательского элемента управления графиками и диаграммами под названием NetworkView:
http://www.codeproject.com/Articles/182683/NetworkView-A-WPF-custom-control-for-visualizing-a