Handle editable hierarchical data / TreeView~DataGrid hybrid

前端 未结 5 1710
感动是毒
感动是毒 2020-12-13 06:40

I am looking for a WPF control which is a hybrid of TreeView and DataGrid, something like the Visual Studio debugger or QuickBooks contacts list etc.

Any other solut

相关标签:
5条回答
  • 2020-12-13 07:19

    Way late to this party but SO says this thread was active just 2 months ago. I don't see how as none of the comments date that recently - but I'll offer this anyway because I just found it and wanted to share the answer in case anyone else is looking for it as well.

    I found this on CodeProject - which is all wrapped up in a nice neat little package. So far, seems to work flawlessly. (and ps: how is this not a normal WPF thing already? I can do it in WinForms with any number of controls that do it automatically)

    Here's the link - hope it helps: https://www.codeproject.com/Articles/1213466/WPF-TreeGrid-using-a-DataGrid

    0 讨论(0)
  • 2020-12-13 07:22

    Following answer is developed from @Robert Rossney's answer:

    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);
            }
        }
    }
    

    The above class is what he explained. Use the Data object in the DataGridHierarchialDataModel to place in your own custom data, and generate your hierarchial data and place it in DataGridHierarchialDatas RawData. Call Initialize when everythings done;

    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;
    

    ^ This was my scenario, to group Accounts

    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 but trust me, Robert Rossney's idea is a blast :) Also, expander '+', '-' Styles are also included (commented out) Hope it helps :)

    0 讨论(0)
  • 2020-12-13 07:22

    just have a look at this control

    http://www.codeproject.com/KB/WPF/wpf_treelistview_control.aspx

    0 讨论(0)
  • 2020-12-13 07:29

    This seems to me like a reasonably straightforward thing to implement if you design your view model properly.

    You basically design the items the same way you would if displaying them in a normal data grid, i.e. each item has a property for each column. In all likelihood, your underlying data model is hierarchical, but the collection that the grid is bound to is going to be flattened, i.e. will contain an item for each node in the hierarchy irrespective of parent/child relationships.

    The item view model has some additional properties: Level, Children, IsExpanded, and IsVisible. Level is a count of the node's ancestors, Children contains the child view model nodes, IsExpanded is used in the UI, and IsVisible is true if the node is visible. It also implements a property called VisibleDescendants:

    public IEnumerable<NodeViewModel> VisibleDescendants
    {
       get
       {
          return Children
                 .Where(x => x.IsVisible)
                 .SelectMany(x => (new[] {x}).Concat(x.VisibleDescendants)));
       }
    }
    

    You use Level, HasChildren, and IsExpanded in the style for the item in the control's first column: they control the left margin and what kind of icon (if any) is displayed.

    You also need to implement ExpandCommand and CollapseCommand properties. The ExpandCommand is enabled if Children.Any() is true and IsExpanded is false, and the CollapseCommand is enabled if Children.Any() is true and IsExpanded is true. These commands, when executed, change the value of IsExpanded.

    And here's where it gets interesting. The simple way to implement this may work for you: the items are exposed by a parent view model whose Items property is not a collection. Instead, it's an enumerator that travels down the chain of child view models and yields only the visible nodes:

    public IEnumerable<NodeViewModel> Items
    {
       get
       {
          return _Items
                 .Where(x => x.IsVisible)
                 .SelectMany(x => (new[] {x}).Concat(x.VisibleDescendants));
       }
    }
    

    Whenever any descendant's IsVisible property changes, the parent view model raises PropertyChanged for the Items property, which forces the data grid to repopulate.

    There's a less simple implementation too, where you make the Items property a class that implements INotifyCollectionChanged, and that raises the proper CollectionChanged events when descendant nodes become visible/invisible, but you only want to go there if performance is an issue.

    0 讨论(0)
  • 2020-12-13 07:36

    I've found that the best MVVM approach is possible with this control: http://blogs.msdn.com/b/atc_avalon_team/archive/2006/03/01/541206.aspx

    To use it with hierarchical view models, you can use hierarchical data template and view model guide from here: http://www.codeproject.com/Articles/24973/TreeListView

    0 讨论(0)
提交回复
热议问题