Ответ 1
просто взгляните на этот элемент управления
http://www.codeproject.com/KB/WPF/wpf_treelistview_control.aspx
Я ищу элемент управления WPF, который является гибридом TreeView и DataGrid, что-то вроде отладчика Visual Studio или списка контактов QuickBooks и т.д.
Любое другое решение о том, как обрабатывать редактируемые иерархические данные в WPF, также будет приветствоваться.
просто взгляните на этот элемент управления
http://www.codeproject.com/KB/WPF/wpf_treelistview_control.aspx
Мне кажется, что это очень простая вещь для реализации, если вы правильно спроектируете свою модель представления.
В основном вы создаете элементы так же, как если бы их отображали в обычной сетке данных, т.е. каждый элемент имеет свойство для каждого столбца. По всей вероятности, ваша базовая модель данных является иерархической, но коллекция, с которой привязана сетка, будет сглажена, т.е. Будет содержать элемент для каждого node в иерархии, независимо от отношений между родителями и дочерними элементами.
Модель представления позиций имеет некоторые дополнительные свойства: Level
, Children
, IsExpanded
и IsVisible
. Level
- это подсчет предков node, Children
содержит узлы модели дочернего представления, IsExpanded
используется в пользовательском интерфейсе, а IsVisible
- true, если видна node. Он также реализует свойство под названием VisibleDescendants
:
public IEnumerable<NodeViewModel> VisibleDescendants
{
get
{
return Children
.Where(x => x.IsVisible)
.SelectMany(x => (new[] {x}).Concat(x.VisibleDescendants)));
}
}
Вы используете Level
, HasChildren
и IsExpanded
в стиле элемента в первом столбце элемента управления: они управляют левым полем и отображается значок (если есть).
Вам также необходимо реализовать свойства ExpandCommand
и CollapseCommand
. ExpandCommand
активируется, если Children.Any()
истинно, а IsExpanded
- false, а CollapseCommand
активируется, если Children.Any()
истинно, а IsExpanded
- true. Эти команды при выполнении изменяют значение IsExpanded
.
И здесь, где это становится интересным. Простой способ реализовать это может сработать для вас: элементы отображаются родительской моделью представления, свойство Items
не является коллекцией. Вместо этого он перечислитель, который перемещается вниз по цепочке моделей просмотра детей и выводит только видимые узлы:
public IEnumerable<NodeViewModel> Items
{
get
{
return _Items
.Where(x => x.IsVisible)
.SelectMany(x => (new[] {x}).Concat(x.VisibleDescendants));
}
}
Всякий раз, когда изменяется свойство потомка IsVisible
, модель родительского представления повышает PropertyChanged
для свойства Items
, что заставляет сетку данных повторно заселяться.
Здесь также реализована менее простая реализация, в которой свойство Items
представляет класс, реализующий INotifyCollectionChanged
, и вызывает правильные события CollectionChanged
, когда узлы-потомки становятся видимыми/невидимыми, но вы только хотите туда попасть если производительность является проблемой.
Следующий ответ разработан из ответа @Robert Rossney:
public class DataGridHierarchialDataModel
{
public DataGridHierarchialDataModel() { Children = new List<DataGridHierarchialDataModel>(); }
public DataGridHierarchialDataModel Parent { get; set; }
public DataGridHierarchialData DataManager { get; set; }
public void AddChild(DataGridHierarchialDataModel t)
{
t.Parent = this;
Children.Add(t);
}
#region LEVEL
private int _level = -1;
public int Level
{
get
{
if (_level == -1)
{
_level = (Parent != null) ? Parent.Level + 1 : 0;
}
return _level;
}
}
#endregion
public bool IsExpanded
{
get { return _expanded; }
set
{
if (_expanded != value)
{
_expanded = value;
if (_expanded == true)
Expand();
else
Collapse();
}
}
}
public bool IsVisible
{
get { return _visible; }
set
{
if (_visible != value)
{
_visible = value;
if (_visible)
ShowChildren();
else
HideChildren();
}
}
}
public bool HasChildren { get { return Children.Count > 0; } }
public List<DataGridHierarchialDataModel> Children { get; set; }
public object Data { get; set; } // the Data (Specify Binding as such {Binding Data.Field})
public IEnumerable<DataGridHierarchialDataModel> VisibleDescendants
{
get
{
return Children
.Where(x => x.IsVisible)
.SelectMany(x => (new[] {x}).Concat(x.VisibleDescendants));
}
}
// Expand Collapse
private bool _expanded = false;
private bool _visible = false;
private void Collapse()
{
DataManager.RemoveChildren(this);
foreach (DataGridHierarchialDataModel d in Children)
d.IsVisible = false;
}
private void Expand()
{
DataManager.AddChildren(this);
foreach (DataGridHierarchialDataModel d in Children)
d.IsVisible = true;
}
// Only if this is Expanded
private void HideChildren()
{
if (IsExpanded)
{
// Following Order is Critical
DataManager.RemoveChildren(this);
foreach (DataGridHierarchialDataModel d in Children)
d.IsVisible = false;
}
}
private void ShowChildren()
{
if (IsExpanded)
{
// Following Order is Critical
DataManager.AddChildren(this);
foreach (DataGridHierarchialDataModel d in Children)
d.IsVisible = true;
}
}
}
public class DataGridHierarchialData : ObservableCollection<DataGridHierarchialDataModel>
{
public List<DataGridHierarchialDataModel> RawData { get; set; }
public DataGridHierarchialData() { RawData = new List<DataGridHierarchialDataModel>(); }
public void Initialize()
{
this.Clear();
foreach (DataGridHierarchialDataModel m in RawData.Where(c => c.IsVisible).SelectMany(x => new[] { x }.Concat(x.VisibleDescendants)))
{
this.Add(m);
}
}
public void AddChildren(DataGridHierarchialDataModel d)
{
if (!this.Contains(d))
return;
int parentIndex = this.IndexOf(d);
foreach (DataGridHierarchialDataModel c in d.Children)
{
parentIndex += 1;
this.Insert(parentIndex, c);
}
}
public void RemoveChildren(DataGridHierarchialDataModel d)
{
foreach (DataGridHierarchialDataModel c in d.Children)
{
if (this.Contains(c))
this.Remove(c);
}
}
}
Этот класс объясняется тем, что он объяснил.
Используйте объект Data
в DataGridHierarchialDataModel
, чтобы поместить свои собственные данные и сгенерировать ваши иерархические данные и поместить их в DataGridHierarchialData
RawData
. Вызывайте Initialize
, когда все делается;
DataTable accTable = await DB.getDataTable("SELECT * FROM Fm3('l1')");
accTable.DefaultView.Sort = "iParent";
DataGridHierarchialData data = new DataGridHierarchialData();
Action<DataRowView, DataGridHierarchialDataModel> Sort = null;
Sort = new Action<DataRowView, DataGridHierarchialDataModel>((row, parent) =>
{
DataGridHierarchialDataModel t = new DataGridHierarchialDataModel() { Data = row, DataManager = data };
if (row["iGroup"].ToString() == "1")
{
foreach (DataRowView r in accTable.DefaultView.FindRows(row["iSmajId"]))
Sort(r, t);
}
parent.AddChild(t);
});
foreach (DataRowView r in accTable.DefaultView.FindRows(0))
{
DataGridHierarchialDataModel t = new DataGridHierarchialDataModel() { Data = r, DataManager = data };
if (r["iGroup"].ToString() == "1")
{
foreach (DataRowView rf in accTable.DefaultView.FindRows(r["iSmajId"]))
Sort(rf, t);
}
t.IsVisible = true; // first layer
data.RawData.Add(t);
}
data.Initialize();
dg.ItemsSource = data;
^ Это был мой сценарий, чтобы сгруппировать учетные записи
XAML:
<DataGrid x:Name="dg" AutoGenerateColumns="False" IsReadOnly="False" CanUserAddRows="False" GridLinesVisibility="All" ColumnWidth="*">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Data.sName}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource MetroDataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCell">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="Expander"
Margin="{Binding Level,Converter={StaticResource LevelToIndentConverter}}"
IsChecked="{Binding Path=IsExpanded, UpdateSourceTrigger=PropertyChanged}"
ClickMode="Press" >
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="19"/>
<Setter Property="Height" Value="13"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Width="19" Height="13" Background="Transparent">
<Border Width="9" Height="9"
BorderThickness="0"
BorderBrush="#FF7898B5"
CornerRadius="1"
SnapsToDevicePixels="true">
<Border.Background>
<SolidColorBrush Color="Transparent"/>
<!--
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="White"
Offset=".2"/>
<GradientStop Color="#FFC0B7A6"
Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
-->
</Border.Background>
<Path x:Name="ExpandPath"
Data="M0,0 L0,6 L6,0 z"
Fill="Transparent"
Stroke="{DynamicResource BlackBrush}" Margin="1,2,1,1">
<Path.RenderTransform>
<RotateTransform Angle="135"
CenterY="3"
CenterX="3" />
</Path.RenderTransform>
</Path>
<!--
<Path x:Name="ExpandPath"
Margin="1,1,1,1"
Fill="Black"
Data="M 0 2 L 0 3 L 2 3 L 2 5 L 3 5 L 3 3 L 5 3 L 5 2 L 3 2 L 3 0 L 2 0 L 2 2 Z"/>
-->
</Border>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter Property="RenderTransform"
TargetName="ExpandPath">
<Setter.Value>
<RotateTransform Angle="180"
CenterY="3"
CenterX="3" />
</Setter.Value>
</Setter>
<Setter Property="Fill"
TargetName="ExpandPath"
Value="{DynamicResource GrayBrush1}" />
<Setter Property="Stroke"
TargetName="ExpandPath"
Value="{DynamicResource BlackBrush}" />
<!--
<Setter Property="Data"
TargetName="ExpandPath"
Value="M 0 2 L 0 3 L 5 3 L 5 2 Z"/>
-->
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.Style>
</ToggleButton>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding HasChildren}" Value="False">
<Setter TargetName="Expander" Property="Visibility" Value="Hidden"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Code" Binding="{Binding Data.sCode}"/>
<DataGridTextColumn Header="Type" Binding="{Binding Data.c867x1}"/>
</DataGrid.Columns>
</DataGrid>
Thats Big: P, но поверьте мне, идея Роберта Россни - это взрыв:) Кроме того, включены расширения '+', '-' Styles (закомментированы) Надеюсь, это поможет:)
Я обнаружил, что с этим контролем возможен лучший подход MVVM: http://blogs.msdn.com/b/atc_avalon_team/archive/2006/03/01/541206.aspx
Чтобы использовать его в иерархических моделях просмотра, вы можете использовать иерархический шаблон данных и руководство по модели просмотра: http://www.codeproject.com/Articles/24973/TreeListView
Уже поздно на эту вечеринку, но SO говорит, что эта тема была активна всего 2 месяца назад. Я не вижу, как ни один из комментариев не датировал это недавно - но я все равно предложу это, потому что я только нашел это и хотел поделиться ответом в случае, если кто-то еще ищет его.
Я нашел это в CodeProject - все это завернуто в симпатичную небольшую упаковку. Пока что, похоже, работает без нареканий. (и PS: как это уже не нормальная вещь WPF? Я могу сделать это в WinForms с любым количеством элементов управления, которые делают это автоматически)
Вот ссылка - надеюсь, это поможет: https://www.codeproject.com/Articles/1213466/WPF-TreeGrid-using-a-DataGrid