Обработка редактируемых иерархических данных / TreeView ~ DataGrid hybrid - PullRequest
21 голосов
/ 11 августа 2010

Я ищу элемент управления WPF, который представляет собой гибрид TreeView и DataGrid, что-то вроде отладчика Visual Studio или списка контактов QuickBooks и т. Д.

Любое другое решение о том, как обрабатывать редактируемые иерархические данные в WPF, будетбыть очень приветливым.

enter image description here

Ответы [ 5 ]

11 голосов
/ 26 марта 2011

Мне кажется, что это довольно просто реализовать, если вы правильно проектируете модель представления.

Вы в основном проектируете элементы так же, как если бы отображали их в обычной сетке данных, т.е. каждый элементимеет свойство для каждого столбца.По всей вероятности, ваша базовая модель данных является иерархической, но коллекция, к которой привязана сетка, будет выровнена, то есть будет содержать элемент для каждого узла в иерархии независимо от отношений родитель / потомок.

Модель представления элемента имеет некоторые дополнительные свойства: Level, Children, IsExpanded и IsVisible.Level - это число предков узла, Children содержит дочерние узлы модели представления, 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() - true, а IsExpanded - false, а CollapseCommand -, если Children.Any() - true, а 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когда узлы-потомки становятся видимыми / невидимыми, но вам нужно идти туда только в случае проблем с производительностью.

5 голосов
/ 13 декабря 2015

Следующий ответ разработан из ответа @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>

Thats Big: P, но поверьте мне, идея Роберта Россни - это взрыв :) Кроме того, расширитель '+', '-' Стили также включены (закомментировано) Надеюсь, это поможет:)

2 голосов
/ 11 августа 2010

просто взгляните на этот элемент управления

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

0 голосов
/ 14 мая 2019

Уже поздно на эту вечеринку, но SO говорит, что эта тема была активна всего 2 месяца назад. Я не вижу, как ни один из комментариев не датировал это недавно - но я все равно предложу это, потому что я просто нашел его и хотел поделиться ответом на тот случай, если кто-то еще его тоже ищет.

Я нашел это в CodeProject - все это упаковано в симпатичную аккуратную упаковку. Пока что, похоже, работает без нареканий. (и PS: как это уже не нормальная вещь WPF? Я могу сделать это в WinForms с любым количеством элементов управления, которые делают это автоматически)

Вот ссылка - надеюсь, это поможет: https://www.codeproject.com/Articles/1213466/WPF-TreeGrid-using-a-DataGrid

0 голосов
/ 23 апреля 2015

Я обнаружил, что лучший подход MVVM возможен с этим элементом управления: http://blogs.msdn.com/b/atc_avalon_team/archive/2006/03/01/541206.aspx

Чтобы использовать его с моделями иерархического представления, вы можете использовать шаблон иерархических данных и просмотреть руководство модели отсюда: http://www.codeproject.com/Articles/24973/TreeListView

...