我正在寻找一个WPF控件,它是TreeView和DataGrid的混合体,类似于Visual Studio调试器或QuickBooks联系人列表等。
关于如何在WPF中处理可编辑分层数据的任何其他解决方案也将非常受欢迎。
如果你正确地设计你的视图模型,在我看来这似乎是一个相当简单的事情。
您基本上设计项目的方式与在普通数据网格中显示项目时的方式相同,即每个项目都有每列的属性。很可能,您的基础数据模型是分层的,但网格绑定的集合将被展平,即将包含层次结构中每个节点的项目,而不管父/子关系如何。
项目视图模型具有一些其他属性:Level
,Children
,IsExpanded
和IsVisible
。 Level
是节点祖先的计数,Children
包含子视图模型节点,在UI中使用IsExpanded
,如果节点可见,则IsVisible
为true。它还实现了一个名为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
,如果CollapseCommand
为真且Children.Any()
为真,则启用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
事件,但是如果性能是个问题,你只想去那里。
以下答案来自@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但是相信我,Robert Rossney的想法是一个爆炸:)此外,扩展器'+',' - '样式也包括在内(注释掉)希望它有帮助:)
我发现使用这种控件可以实现最好的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