【问题标题】:Databinding with TreeView in WPF在 WPF 中使用 TreeView 进行数据绑定
【发布时间】:2018-03-14 17:13:15
【问题描述】:

我正在使用数据绑定和树视图,但无法让 TreeView 填充到我的 WPF 中。我想我比较接近,只是某处的一个小调整,但我似乎找不到它。

这是我的项目类:

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

        }
    }
}

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

<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>

编辑:

这是属性类:

 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>

所以,这似乎并没有触发 change 事件。我知道这一点,因为路径设置为名称 + ">"。当我更改名称时,路径不会反映更改。如果有意义的话,它只会显示我之前的 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));
        }
    }

【问题讨论】:

  • 您不能绑定到在方法本地定义的属性。您需要将其公开为 DependencyProperty,并将 MainWindow 设置为自身的 DataContext。

标签: c# wpf xaml data-binding treeview


【解决方案1】:

这里有一些问题。

ItemsSource="{Binding parents}"

这里是parents:

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

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

如果要绑定到parents,有几个要求:必须是……

  1. public...
  2. 属性(不是字段——它需要一个get 块)...
  3. TreeView 的 DataContext 是任何对象。

这些都不是真的。

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

  1. 将其设为ObservableCollection&lt;T&gt; 而不是List&lt;T&gt;,这样它就会通知UI 添加或删除的项目。
  2. 拥有它的类应该是视图模型类,而不是您的窗口/用户控件。当我们说“viewmodel”时,我们的意思是它实现INotifyPropertyChanged 并在其属性值更改时引发PropertyChanged。同样,这是为了让 UI 了解更改。

让 UI 保持通知是绑定的全部意义所在:它们侦听视图模型中的更改并更新 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 类重写为从ViewModelBase 派生的ProjectViewModel,使其以同样的方式提升PropertyChanged,并使用ObservableCollection&lt;Project&gt; 来表示Children。

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

public MainWindow()
{
    InitializeComponent();

    var vm = new MainViewModel();
    vm.BuildData();
    DataContext = vm;
}

您的 XAML 也需要一些工作。

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

这是新的 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 代码中。如果您使用视图模型,它是世界上最简单的改变您的 UI 的方法。也许您希望主窗口的原始内容只是主窗口中的三个标签页之一。适当地保持代码分离使这种事情变得更加简单。

【讨论】:

  • 评论不适用于扩展讨论或调试会话;这个对话是moved to chat。请记住将任何相关信息重新合并到答案中。
猜你喜欢
  • 1970-01-01
  • 2011-09-12
  • 2014-08-09
  • 1970-01-01
  • 2011-01-19
  • 2022-09-30
  • 2011-03-21
  • 2011-06-16
相关资源
最近更新 更多