Подтвердить что ты не робот

Обработка редактируемых иерархических данных /TreeView ~ DataGrid hybrid

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

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

enter image description here

4b9b3361

Ответ 2

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

В основном вы создаете элементы так же, как если бы их отображали в обычной сетке данных, т.е. каждый элемент имеет свойство для каждого столбца. По всей вероятности, ваша базовая модель данных является иерархической, но коллекция, с которой привязана сетка, будет сглажена, т.е. Будет содержать элемент для каждого 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, когда узлы-потомки становятся видимыми/невидимыми, но вы только хотите туда попасть если производительность является проблемой.

Ответ 3

Следующий ответ разработан из ответа @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 (закомментированы) Надеюсь, это поможет:)

Ответ 4

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

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

Ответ 5

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

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

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