Необходимо создать контейнер компоновки (SizeableStackPanel), который будет работать как стандартный StackPanel но кроме всего прочего будет автоматически добавлять GridSplitter между элементами контейнера. Соответственно элементам внутри контейнера добавляется прикреплённое свойство типа GridLength.
Представляю это как Custom Control производный от ItemsControl, внутри которого заключён Grid, в нечётных строках (или столбцах, в зависимости от Orientation) располагаются элементы контейнера, а в чётных — GridSplitter'ы. Количество строк (столбцов) рассчитывается автоматически в зависимости от количества элементов контейнера.
это больше похоже не на StackPanel, а на Grid. Смотрите рефлектором код грида и, например, ColumnDefinitions, как они там размеры со звездочкой обрабатывают. А GridSplitter должен менять размеры? Тогда тем более — грид нужен, возможно даже просто от грида унаследоваться (если, конечно, речь про WPF/Silverlight). Хотя в Сильверлайте будут сложности с наследованием, там много методов, которые нужно бы по уму переопределить, а они зачем-то сделаны sealed.
Re[2]: Свой контейнер компоновки
От:
Аноним
Дата:
08.07.10 04:49
Оценка:
Здравствуйте, notacat, Вы писали:
N>это больше похоже не на StackPanel, а на Grid
Нет. По поведению должно быть похоже именно на StackPanel: Добавленные элементы стыкуются по принципу стека один за другим, а не вставляются в таблицу. А то, что реализацию я представляю именно на основе Grid я тоже написал.
N>возможно даже просто от грида унаследоваться
Унаследовавшись от грида придётся перекрывать доступ к многим ненужным свойствам и методам, например закрыть абсолютно ненужные RowDefinition и ColumnDefinition.
Что-то уже начало получаться. Наверняка есть куча косяков, но по крайней мере в основном работает.
Сделал так:
public class SizeableStackPanel : ItemsControl {
// Определение прикреплённого SizeProperty
// Определение OrientationProperty
// Определение SplitterWidthProperty
// Определение SplitterBrushPropertypublic override void OnApplyTemplate() {
base.OnApplyTemplate();
OnItemsChanged(null);
}
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) {
base.OnItemsChanged(e);
switch(Orientation) {
case Orientation.Vertical: {
RowsUpdate();
break;
}
case Orientation.Horizontal: {
ColumnsUpdate();
break;
}
}
}
private void RowsUpdate() {
if(this.Template == null) return;
Grid grid = (Grid)this.Template.FindName("grid", this);
if(grid == null) return;
grid.Children.Clear();
grid.RowDefinitions.Clear();
int i = 0;
foreach(UIElement element in Items) {
if(i > 0) {
RowDefinition d2 = new RowDefinition();
d2.Height = GridLength.Auto;
grid.RowDefinitions.Add(d2);
GridSplitter splitter = new GridSplitter();
Binding b1 = new Binding("SplitterWidth");
b1.Source = this;
splitter.SetBinding(GridSplitter.HeightProperty, b1);
Binding b2 = new Binding("SplitterBrush");
b2.Source = this;
splitter.SetBinding(GridSplitter.BackgroundProperty, b2);
splitter.VerticalAlignment = VerticalAlignment.Center;
splitter.HorizontalAlignment = HorizontalAlignment.Stretch;
grid.Children.Add(splitter);
Grid.SetRow(splitter, i++);
}
RowDefinition rd = new RowDefinition();
rd.Height = GetSizeProperty(element);
grid.RowDefinitions.Add(rd);
this.RemoveLogicalChild(element);
grid.Children.Add(element);
Grid.SetRow(element, i++);
}
}
// Определение ColumnsUpdate()
}
Возможно я что-то использую не так, как следует. Мне не нравятся следующие моменты:
1. this.RemoveLogicalChild(element); А правильно ли так удалять элемент?
2. Grid grid = (Grid)this.Template.FindName("grid", this); Может Grid следует объявить в коде?
Здравствуйте, Аноним, Вы писали:
А>Необходимо создать контейнер компоновки (SizeableStackPanel), который будет работать как стандартный StackPanel но кроме всего прочего будет автоматически добавлять GridSplitter между элементами контейнера. Соответственно элементам внутри контейнера добавляется прикреплённое свойство типа GridLength. А>Представляю это как Custom Control производный от ItemsControl, внутри которого заключён Grid, в нечётных строках (или столбцах, в зависимости от Orientation) располагаются элементы контейнера, а в чётных — GridSplitter'ы. Количество строк (столбцов) рассчитывается автоматически в зависимости от количества элементов контейнера.
А>В XAML должно выглядеть примерно так: А>
А>Прошу помочь с реализацией. А>А может есть уже где нибудь такой контрол?
1.Пишем юзерконтрол,состоящий из грида.
2. Добавляем ObservableCollection для элементов.
3. Подписываемся на изменение коллекции элементов и изменение свойства, содержащего коллекцию.
4. В обработчике событий вышеуказанных вызываем метод, осуществляющий работу с гридом, т.е.:
— расставление сплиттеров, задание числа строк(или столбцов, в зависимости от ориентации).
5. Profit.
Это один из вариантов, который не предполагает наследования. Реализуется часов за несколько на живую нитку.
Re[2]: Свой контейнер компоновки
От:
Аноним
Дата:
08.07.10 06:31
Оценка:
Здравствуйте, Codechanger, Вы писали:
C>1.Пишем юзерконтрол,состоящий из грида. C>2. Добавляем ObservableCollection для элементов. C>3. Подписываемся на изменение коллекции элементов и изменение свойства, содержащего коллекцию. C>4. В обработчике событий вышеуказанных вызываем метод, осуществляющий работу с гридом, т.е.: C> — расставление сплиттеров, задание числа строк(или столбцов, в зависимости от ориентации). C>5. Profit.
C>Это один из вариантов, который не предполагает наследования. Реализуется часов за несколько на живую нитку.
UserControl не подходит, т.к. он производный от ContentControl и не позволит в XAML конструкцию вида:
Ну нет, если у вас ItemsControl, то сделайте нормально. Вы с ним работаете, как с панелью, а надо — как с ItemsControl'ом.
Т.е. пусть у вас Items будут Items, не надо никаких RemoveLogicalChild. Grid — не дочерний элемент, а ItemsPanel (наверное нужно запретить менять это свойство). Возможно имеет смысл сделать так, чтобы IsItemItsOwnContainerOverride возвращал true.
А>2. Grid grid = (Grid)this.Template.FindName("grid", this); Может Grid следует объявить в коде?
ItemsPanelTemplate MS обычно грузит из кода.
Здравствуйте, Аноним, Вы писали:
А>Необходимо создать контейнер компоновки (SizeableStackPanel), который будет работать как стандартный StackPanel но кроме всего прочего будет автоматически добавлять GridSplitter между элементами контейнера. Соответственно элементам внутри контейнера добавляется прикреплённое свойство типа GridLength. А>Представляю это как Custom Control производный от ItemsControl, внутри которого заключён Grid, в нечётных строках (или столбцах, в зависимости от Orientation) располагаются элементы контейнера, а в чётных — GridSplitter'ы. Количество строк (столбцов) рассчитывается автоматически в зависимости от количества элементов контейнера.
А>В XAML должно выглядеть примерно так: А>
А>Прошу помочь с реализацией. А>А может есть уже где нибудь такой контрол?
1. Наследуетесь от Panel
2. В OnVisualChildrenChanged отлавливаете событие добавления контрола и сами добавляете GridSplitter или свой контрол для ресайзинга
3. Переопределяете метод MeasureOverride
4. Переопределяете метод ArrangeOverride
Здравствуйте, Аноним, Вы писали:
А>Необходимо создать контейнер компоновки (SizeableStackPanel), который будет работать как стандартный StackPanel но кроме всего прочего будет автоматически добавлять GridSplitter между элементами контейнера. Соответственно элементам внутри контейнера добавляется прикреплённое свойство типа GridLength. А>Представляю это как Custom Control производный от ItemsControl, внутри которого заключён Grid, в нечётных строках (или столбцах, в зависимости от Orientation) располагаются элементы контейнера, а в чётных — GridSplitter'ы. Количество строк (столбцов) рассчитывается автоматически в зависимости от количества элементов контейнера.
Я бы просто заменил панель ItemsControl'а на AutoGrid (легко гуглится), а в шаблоне контейнера элементов добавлял снизу GridSplitter — и всё.
А>Прошу помочь с реализацией. А>А может есть уже где нибудь такой контрол?
Это очень похоже на Accordion. В любом случаем, делать свой контрол надо по его образу и подобию, а не городить монстра из панелей.
Здравствуйте, Vladek, Вы писали:
V>Я бы просто заменил панель ItemsControl'а на AutoGrid (легко гуглится), а в шаблоне контейнера элементов добавлял снизу GridSplitter — и всё.
Здравствуйте, Codechanger, Вы писали:
C>1.Пишем юзерконтрол,состоящий из грида. C>2. Добавляем ObservableCollection для элементов. C>3. Подписываемся на изменение коллекции элементов и изменение свойства, содержащего коллекцию. C>4. В обработчике событий вышеуказанных вызываем метод, осуществляющий работу с гридом, т.е.: C> — расставление сплиттеров, задание числа строк(или столбцов, в зависимости от ориентации). C>5. Profit.
Сделал, всё работает, но только если использовать в одном экземпляре. Как только добавляю второй:
сразу начинает ругаться "Указанный элемент уже является логическим дочерним для другого элемента. Сначала отсоедините его." при вызове grid.Children.Add(element)
Такое ощущение, что вторая кнопка каким-то образом добавляется в логическое дерево первой панели, и при попытки добавить её во вторую панель выбрасывает исключение.
Здравствуйте, dx.dash, Вы писали:
DD>Здравствуйте, Codechanger, Вы писали:
C>>1.Пишем юзерконтрол,состоящий из грида. C>>2. Добавляем ObservableCollection для элементов. C>>3. Подписываемся на изменение коллекции элементов и изменение свойства, содержащего коллекцию. C>>4. В обработчике событий вышеуказанных вызываем метод, осуществляющий работу с гридом, т.е.: C>> — расставление сплиттеров, задание числа строк(или столбцов, в зависимости от ориентации). C>>5. Profit.
DD>Сделал, всё работает, но только если использовать в одном экземпляре. Как только добавляю второй: DD>
DD>сразу начинает ругаться "Указанный элемент уже является логическим дочерним для другого элемента. Сначала отсоедините его." при вызове grid.Children.Add(element) DD>Такое ощущение, что вторая кнопка каким-то образом добавляется в логическое дерево первой панели, и при попытки добавить её во вторую панель выбрасывает исключение.
Код в студию.
Re[4]: Свой контейнер компоновки
От:
Аноним
Дата:
09.07.10 08:38
Оценка:
Здравствуйте, Codechanger, Вы писали:
C>Код в студию.
public partial class SizeableStackPanel : UserControl {
public static readonly DependencyProperty ItemsProperty;
public ObservableCollection<UIElement> Items {
get { return (ObservableCollection<UIElement>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty SizeProperty;
public static void SetSize(UIElement element, GridLength value) {
element.SetValue(SizeProperty, value);
}
public static GridLength GetSize(UIElement element) {
return (GridLength)element.GetValue(SizeProperty);
}
public static readonly DependencyProperty SplitterWidthProperty;
public Double SplitterWidth {
get { return (Double)GetValue(SplitterWidthProperty); }
set { SetValue(SplitterWidthProperty, value); }
}
public static readonly DependencyProperty SplitterBrushProperty;
public Brush SplitterBrush {
get { return (Brush)GetValue(SplitterBrushProperty); }
set { SetValue(SplitterBrushProperty, value); }
}
public static readonly DependencyProperty OrientationProperty;
public Orientation Orientation {
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
static SizeableStackPanel() {
ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<UIElement>), typeof(SizeableStackPanel), new PropertyMetadata(new ObservableCollection<UIElement>(), new PropertyChangedCallback(OnItemsPropertyChanged)));
SizeProperty = DependencyProperty.RegisterAttached("Size", typeof(GridLength), typeof(SizeableStackPanel));
SplitterWidthProperty = DependencyProperty.Register("SplitterWidth", typeof(Double), typeof(SizeableStackPanel), new PropertyMetadata(Double.NaN));
SplitterBrushProperty = DependencyProperty.Register("SplitterBrush", typeof(Brush), typeof(SizeableStackPanel));
OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(SizeableStackPanel), new PropertyMetadata(Orientation.Vertical, new PropertyChangedCallback(OnOrientationPropertyChanged)));
}
public SizeableStackPanel() {
InitializeComponent();
Items.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Items_CollectionChanged);
}
void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
Update();
}
private static void OnOrientationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
SizeableStackPanel element = d as SizeableStackPanel;
if(element != null) {
element.Update();
}
}
private static void OnItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
SizeableStackPanel element = d as SizeableStackPanel;
if(element != null) {
element.Update();
}
}
private void Update() {
grid.Children.Clear();
grid.RowDefinitions.Clear();
grid.ColumnDefinitions.Clear();
int i = 0;
if(Orientation == Orientation.Vertical) {
foreach(UIElement element in Items) {
if(i > 0) {
RowDefinition d2 = new RowDefinition();
d2.Height = GridLength.Auto;
grid.RowDefinitions.Add(d2);
GridSplitter splitter = new GridSplitter();
Binding b1 = new Binding("SplitterWidth");
b1.Source = this;
splitter.SetBinding(GridSplitter.HeightProperty, b1);
Binding b2 = new Binding("SplitterBrush");
b2.Source = this;
splitter.SetBinding(GridSplitter.BackgroundProperty, b2);
splitter.VerticalAlignment = VerticalAlignment.Center;
splitter.HorizontalAlignment = HorizontalAlignment.Stretch;
grid.Children.Add(splitter);
Grid.SetRow(splitter, i++);
}
RowDefinition rd = new RowDefinition();
Binding b = new Binding();
b.Path = new PropertyPath(SizeableStackPanel.SizeProperty);
b.Source = element;
b.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(rd, RowDefinition.HeightProperty, b);
grid.RowDefinitions.Add(rd);
grid.Children.Add(element);
Grid.SetRow(element, i++);
}
} else {
foreach(UIElement element in Items) {
if(i > 0) {
ColumnDefinition d2 = new ColumnDefinition();
d2.Width = GridLength.Auto;
grid.ColumnDefinitions.Add(d2);
GridSplitter splitter = new GridSplitter();
Binding b1 = new Binding("SplitterWidth");
b1.Source = this;
splitter.SetBinding(GridSplitter.WidthProperty, b1);
Binding b2 = new Binding("SplitterBrush");
b2.Source = this;
splitter.SetBinding(GridSplitter.BackgroundProperty, b2);
splitter.VerticalAlignment = VerticalAlignment.Stretch;
splitter.HorizontalAlignment = HorizontalAlignment.Center;
grid.Children.Add(splitter);
Grid.SetColumn(splitter, i++);
}
ColumnDefinition rd = new ColumnDefinition();
Binding b = new Binding();
b.Path = new PropertyPath(SizeableStackPanel.SizeProperty);
b.Source = element;
b.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(rd, ColumnDefinition.WidthProperty, b);
grid.ColumnDefinitions.Add(rd);
grid.Children.Add(element);
Grid.SetColumn(element, i++);
}
}
}
}
может просто у вас грид не тот нашелся, а какой-нибудь снаружи?
Как этот грид объявляется и где, про это в коде нет. А лучше бы полный сэмпл, чтобы поиграться
Здравствуйте, vit_as, Вы писали:
_>1. Наследуетесь от Panel _>2. В OnVisualChildrenChanged отлавливаете событие добавления контрола и сами добавляете GridSplitter или свой контрол для ресайзинга
Здесь надо помнить, что такой сценарий обламается, если панель будут использовать в качестве ItemsPanel для ItemsControl. _>3. Переопределяете метод MeasureOverride _>4. Переопределяете метод ArrangeOverride
Здравствуйте, notacat, Вы писали:
N>может просто у вас грид не тот нашелся, а какой-нибудь снаружи? N>Как этот грид объявляется и где, про это в коде нет. А лучше бы полный сэмпл, чтобы поиграться
Вот пример. Выложил на депозит, больше ничего в голову не пришло: сэмпл