我正在寻找一个WPF控件,它是TreeView和DataGrid的混合体,类似于Visual Studio调试器或QuickBooks联系人列表等。
如何在WPF中处理可编辑的分层数据的任何其他解决方案也将非常受欢迎。
答案 0 :(得分:11)
如果你正确地设计你的视图模型,这对我来说似乎是一个相当简单的事情。
您基本上按照在普通数据网格中显示项目的方式设计项目,即每个项目都有每列的属性。很可能,您的基础数据模型是分层的,但网格绑定的集合将被展平,即将包含层次结构中每个节点的项目,而不管父/子关系。
项目视图模型具有一些其他属性:Level
,Children
,IsExpanded
和IsVisible
。 Level
是节点祖先的计数,Children
包含子视图模型节点,UI中使用IsExpanded
,如果节点可见,则IsVisible
为真。它还实现了一个名为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
为true且Children.Any()
为false,则启用IsExpanded
,如果CollapseCommand
为true且Children.Any()
为true,则启用IsExpanded
。这些命令在执行时会更改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
事件,但是如果表现有问题,你只想去那里。
答案 1 :(得分:5)
以下答案来自@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
s 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>
多数人:P但是相信我,罗伯特罗斯尼的想法是一个爆炸:) 此外,扩展器&#39; +&#39;,&#39; - &#39;样式也包括在内(已注释掉) 希望它有所帮助:)
答案 2 :(得分:2)
答案 3 :(得分:0)
我发现使用此控件可以实现最佳的MVVM方法:http://blogs.msdn.com/b/atc_avalon_team/archive/2006/03/01/541206.aspx
要将其与分层视图模型一起使用,您可以从此处使用分层数据模板和视图模型指南: http://www.codeproject.com/Articles/24973/TreeListView
答案 4 :(得分:0)
参加该聚会的路很晚,但SO表示此线程仅在2个月前处于活动状态。我看不到最近的评论是怎么回事-但我还是会提供此评论,因为我刚刚找到它,并希望分享答案,以防其他人也在寻找它。
我在CodeProject上找到了它-它们全部包装在一个漂亮的小包装中。到目前为止,似乎工作正常。 (和ps:这已经不是普通的WPF了吗?我可以在WinForms中使用任意数量的自动执行此操作的控件来完成此操作)
以下是链接-希望对您有所帮助: https://www.codeproject.com/Articles/1213466/WPF-TreeGrid-using-a-DataGrid