在WPF中使用TreeView进行数据绑定

时间:2017-10-03 13:25:53

标签: c# wpf xaml data-binding treeview

我正在使用数据绑定和树视图,我无法在我的WPF中填充TreeView。我觉得我比较亲密,只是在某个地方稍作调整,但我似乎无法找到它。

这是我的Project类:

public class Project
{
    public Project(string Name, bool isFolder, Project ParentFolder)
    {
        this.Name = Name;
        this.isFolder = isFolder;
        Children = new List<Project>();

        if (ParentFolder == null)
        {
            Path = Name;
        }
        else
        {
            Path = ParentFolder.Path + " > " + Name;
        }
    }
    public string Path { get; private set; }
    public string Name { get; set; }
    public bool isFolder { get; set; }
    public List<Project> Children { get; set; }

    public IEnumerable<Project> ChildFolders
    {
        get
        {
            return Children.Where(p => p.isFolder);
        }
    }

    public object Icon
    {
        get
        {
            if (isFolder)
            {
                return 0; // return folder icon
            }
            else
            {
                return 1; // return project icon
            }
        }
    }

    public IEnumerable<Project> SearchRecursively(string SearchString)
    {
        return GetAllChildren.Where(p => p.Name.Contains(SearchString));
    }

    private List<Project> GetAllChildren
    {
        get
        {
            List<Project> allChildren = new List<Project>();
            foreach(Project child in Children)
            {
                allChildren.AddRange(child.GetAllChildren);
            }
            return allChildren;
        }
    }
}

}

这是我将用于制作测试数据的MaiWindow.xaml.cs类:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.BuildData();
        }

        private void BuildData()
        {
           List<Project> parents = new List<Project>();
            Project parentOne = new Project("Apple", true, null);
            Project parentTwo = new Project("Samsung", true, null);
            Project parentThree = new Project("Google", true, null);
            parents.Add(parentOne); parents.Add(parentTwo); parents.Add(parentThree);

            Project appleMacBook = new Project("Mac", false, parentOne);
            Project appleIpad = new Project("iPad", false, parentOne);
            Project appleiPhone = new Project("iPhone", false, parentOne);

            Project samsungGalaxy = new Project("Galaxy", false, parentTwo);
            Project samsungNote = new Project("Note", false, parentTwo);

            Project googlePixel = new Project("Pixel", false, parentThree);
            Project googleChromecast = new Project("Chromecast", false, parentThree);

            parents[0].Children.Add(appleMacBook); parents[0].Children.Add(appleIpad); parents[0].Children.Add(appleiPhone);
            parents[1].Children.Add(samsungGalaxy); parents[1].Children.Add(samsungNote);
            parents[2].Children.Add(googlePixel); parents[2].Children.Add(googleChromecast); 

        }
    }
}

这是我的XAML,我试图显示TreeView。现在,它只是空白。我会很感激任何提示。

<TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="265"
              ItemsSource="{Binding parents}">
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding parents}" DataType="{x:Type self:Project}">
                <TreeViewItem Header="{Binding Name}"></TreeViewItem>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>

编辑:

这是Property类:

 public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
            OnPropertyChanged("Name");
        }
    }
    private string name { get; set; }
    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        if(PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

XAML:

<TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding ChildFolders}">
                <StackPanel Orientation="Horizontal" >
                    <Image Source="{Binding Icon}" Margin="5, 5, 5, 5"></Image>
                    <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" BorderThickness="0" FontSize="16" Margin="5"/>
                </StackPanel>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>

所以,这似乎并未触发变更事件。我知道这是因为Path设置为Name +&#34;&gt;&#34;。当我更改名称时,路径不反映更改。它只显示我以前的Name值,如果这是有意义的。

if (ParentFolder == null)
        {
            Path = Name;
        }
        else
        {
            Path = ParentFolder.Path + " > " + Name;
        }

编辑:

public Project(string Name, bool isFolder, Project ParentFolder)
    {
        this.Name = Name;
        this.isFolder = isFolder;
        Children = new List<Project>();
        this.ParentFolder = ParentFolder;
    }
    public string Path
    {
        get
        {
            return this.ParentFolder + " > " + this.Name;
        }
        set
        {
            this.Path = Path;
        }
    }

XAML:

<TextBox x:Name="FolderNameBox" Grid.Column="1" Background="White" Grid.Row="1" Grid.ColumnSpan="5" 
               Margin="0,0,287,654.333" VerticalContentAlignment="Center"
               Padding="6" FontSize="16"
               IsReadOnly="True"
               Text="{Binding ElementName=Hierarchy, Path=SelectedItem.Path, UpdateSourceTrigger=PropertyChanged}">
    </TextBox>
    <TextBox x:Name="SearchProjectsBox" Grid.Column="5" Background="White" Grid.Row="1" Text="Search Projects" 
        Margin="47.333,0,0,654.333" VerticalContentAlignment="Center" Foreground="LightGray" Padding="6" FontSize="16" HorizontalAlignment="Left" Width="268" GotFocus="TextBox_GotFocus" LostFocus="TextBox_LostFocus"/>
    <TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="226" 
              ItemsSource="{Binding Projects}">

        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding ChildFolders}">
                <StackPanel Orientation="Horizontal" >
                    <Image Source="{Binding Icon}" Margin="5, 5, 5, 5"></Image>
                    <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" BorderThickness="0" FontSize="16" Margin="5"/>
                </StackPanel>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
    <Grid Grid.ColumnSpan="2" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="245,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="540">
        <ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
            <ListView Margin="0,0,10,0" Name="ProjectView" ItemsSource="{Binding Projects}" FontSize="16" Foreground="Black">
                <ListView.View>
                    <GridView ColumnHeaderContainerStyle="{StaticResource GridHeader}">
                        <GridViewColumn Header="Name" Width="200" DisplayMemberBinding="{Binding ElementName=Hierarchy, Path=SelectedItem.Name, UpdateSourceTrigger=PropertyChanged}"></GridViewColumn>
                        <GridViewColumn Header="Directory" Width="328" DisplayMemberBinding="{Binding ElementName=Hierarchy, Path=SelectedItem.Path, UpdateSourceTrigger=PropertyChanged}"></GridViewColumn>
                    </GridView>
                </ListView.View>
            </ListView>
        </ScrollViewer>
    </Grid>
</Grid>

路径也会更新,但是当我看到它时,它将显示项目的路径,而不是激活的名称更改。它实时变化但不保存字符串值。只记录发生了变化。

继承我的财产变化。

public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        if(PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

1 个答案:

答案 0 :(得分:1)

这里有一些问题。

ItemsSource="{Binding parents}"

这是parents

private void BuildData()
{
    List<Project> parents = new List<Project>();

您要求XAML检查代码隐藏类中的所有方法,查找名为parents的局部变量。这不是一个合理的要求。

如果要绑定到parents,有一些要求:必须是......

  1. A public ...
  2. 属性(不是字段 - 需要get块)...
  3. 无论您的TreeView的DataContext是什么对象。
  4. 这些都不是真的。

    还有两件事 - 不是必需的,但是一个非常好的主意:

    1. 设为ObservableCollection<T>而不是List<T>,以便它会通知UI添加或删除的项目。
    2. 拥有它的类应该是viewmodel类,而不是window / usercontrol。当我们说“viewmodel”时,我们的意思是它实现INotifyPropertyChanged并在其属性值发生变化时引发PropertyChanged。同样,这是关于保持UI通知变化。
    3. 保持UI通知是绑定的全部内容:它们侦听viewmodel中的更改并更新UI。

      所以你需要一个看起来像这样的主视图模型:

      public class ViewModelBase : INotifyPropertyChanged
      {
          public event PropertyChangedEventHandler PropertyChanged;
      
          //  C#6
          /*
          protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
              PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
          */
      
          protected virtual void OnPropertyChanged(string propName)
          {
              var handler = PropertyChanged;
              if (handler != null) {
                  handler(this, new PropertyChangedEventArgs(propName));
              }
          }
      }
      
      public class MainViewModel : ViewModelBase
      {
          private ObservableCollection<Project> _projects;
          public ObservableCollection<Project> Projects {
              get { return _projects; }
              set {
                   if (value != _projects) {
                       _projects = value;
                       OnPropertyChanged(nameof(Projects));
                  }
              }
          }
      
          public void BuildData() {
              Projects = new ObservableCollection<Project>();
      
              //  do the rest of the stuff
          }
      }
      

      您应该将Project作为ProjectViewModel来自ViewModelBase的{​​{1}}重写,并以同样的方式提升PropertyChanged,并使用ObservableCollection<Project> Children

      在你的主窗口......

      public MainWindow()
      {
          InitializeComponent();
      
          var vm = new MainViewModel();
          vm.BuildData();
          DataContext = vm;
      }
      

      您的XAML也需要一些工作。

      1. Projects现在有一个大写名称
      2. 对于项目模板,您将绑定到子项的属性,该属性提供树视图项的子项。这是Children类的Project属性。
      3. datatemplate告诉XAML如何呈现控件的内容。树创建TreeViewItem Project作为其DataContext,然后使用HierarchicalDataTemplateDataContext转换为某种视觉内容。您不使用该模板来创建TreeViewItem;您可以使用它在 TreeViewItem中创建视觉内容
      4. 所以这是新的XAML:

        <TreeView 
            x:Name="Hierarchy" 
            ItemsSource="{Binding Projects}"
            Grid.Column="4" 
            HorizontalAlignment="Left" 
            Height="631" 
            Margin="0,58,0,0" 
            Grid.Row="1" 
            VerticalAlignment="Top" 
            Width="265" 
            >
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <Label Content="{Binding Name}" />
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
        

        没有理由养成DataContext = this;的习惯。一旦你开始,接下来你会知道你将在UserControl中做到这一点并在这里询问为什么在父XAML中对它的所有绑定都被破坏了。依赖属性比INPC更麻烦,你最终得到的代码应该是混合到你的MainWindow代码中的视图模型。如果你使用viewmodelsit这是世界上最简单的东西来改变你的UI。也许您希望主窗口的原始内容只是主窗口中三个标签页中的一个。保持代码正确分离使得这种事情变得更加简单。