简体   繁体   English

如何在 TreeView 上添加复选框

[英]How to add checkboxes on TreeView

I'm new to C# and WPF and I'd like to build an app that can select folders and files and get the path.我是 C# 和 WPF 的新手,我想构建一个可以选择文件夹和文件并获取路径的应用程序。 I was able to show the folder structure and add a checkbox by following the instruction from this link .按照此链接中的说明,我能够显示文件夹结构并添加一个复选框。

在此处输入图像描述

But I'm not sure how can I achieve this.但我不确定如何实现这一目标。

在此处输入图像描述

Here's my code below.下面是我的代码。

WPF WPF

<TreeView Name="MyTreeView" Margin="249,31,20,24" Background="{x:Null}" BorderThickness="0,0,0,0" Cursor="Arrow">
                        <TreeView.Resources>
                            <Style TargetType="{x:Type TreeViewItem}">
                                <Setter Property="HeaderTemplate">
                                    <Setter.Value>
                                        <DataTemplate>
                                            <StackPanel Orientation="Horizontal">
                                                <CheckBox Name="MyCheckBox"/>
                                                    <Image Name="img"  Width="20" Height="20" Stretch="Fill"
                                                    Source="{Binding RelativeSource={RelativeSource 
                                                    Mode=FindAncestor,
                                                    AncestorType={x:Type TreeViewItem}},
                                                    Path=Tag,
                                                    Converter={x:Static local:TagToImageConverter.Instance}}"/>          
                                                <TextBlock Text="{Binding}"/>
                                            </StackPanel>
                                        </DataTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </TreeView.Resources>
                    </TreeView>

C# C#

// Get users' folders
    private void GetUsersFolder_Loaded(object sender, RoutedEventArgs e)
    {
        foreach (string s in Directory.GetDirectories("C:\\Users") )
        {
            if (!s.Contains("All Users") && !s.Contains("Default") && !s.Contains("Default User") && !s.Contains("Public"))
            {
                TreeViewItem user_folders = new TreeViewItem();
                user_folders.Header = s.Substring(s.LastIndexOf("\\") + 1);
                user_folders.Tag = new object[] { PrjRootPath+"icons\\mainpage\\folder.png", s };
                user_folders.FontWeight = FontWeights.Normal;
                user_folders.FontSize = 14;
                user_folders.Items.Add(dummyNode);
                user_folders.Expanded += new RoutedEventHandler(Userfolders_Expanded);
                MyTreeView.Items.Add(user_folders);
            }
        }
    }

    // Get folders inside user's folder
    void Userfolders_Expanded(object sender, RoutedEventArgs e)
    {
        TreeViewItem item = (TreeViewItem)sender;
        if (item.Items.Count == 1 && item.Items[0] == dummyNode)
        {
            item.Items.Clear();
            try
            {
                foreach (string s in Directory.GetDirectories(((Object[])item.Tag)[1].ToString()))
                {
                    if (s.Contains("Desktop") ||
                        s.Contains("Documents") ||
                        s.Contains("Downloads") ||
                        s.Contains("Pictures") ||
                        s.Contains("Contacts") ||
                        s.Contains("Videos"))
                    {
                        TreeViewItem subitem = new TreeViewItem();
                        subitem.Header = s.Substring(s.LastIndexOf("\\") + 1);
                        subitem.Tag = new object[] { PrjRootPath + "icons\\mainpage\\folder.png", s };
                        subitem.FontWeight = FontWeights.Normal;
                        subitem.Items.Add(dummyNode);
                        subitem.Expanded += new RoutedEventHandler(InsideUserfoldersFiles_Expanded);
                        item.Items.Add(subitem);
                    }
                }
            }
            catch (Exception e1) { MessageBox.Show(e1.Message + " " + e1.InnerException);  }
        }
    }

    // Get folders and files inside the folders of user's folder
    void InsideUserfoldersFiles_Expanded(object sender, RoutedEventArgs e)
    {
        TreeViewItem item = (TreeViewItem)sender;
        if (item.Items.Count == 1 && item.Items[0] == dummyNode)
        {
            item.Items.Clear();
            try
            {
                foreach (string s in Directory.GetDirectories(((Object[])item.Tag)[1].ToString()))
                {
                        TreeViewItem subitem = new TreeViewItem();
                        subitem.Header = s.Substring(s.LastIndexOf("\\") + 1);
                        subitem.Tag = new object[] { PrjRootPath + "icons\\mainpage\\folder.png", s };
                        subitem.FontWeight = FontWeights.Normal;
                        subitem.Items.Add(dummyNode);
                        subitem.Expanded += new RoutedEventHandler(InsideUserfoldersFiles_Expanded);
                        item.Items.Add(subitem);
                }

                foreach (string s in Directory.GetFiles(((Object[])item.Tag)[1].ToString()))
                {
                    string f_extention = s.Substring(s.LastIndexOf(".") + 1);
                    
                    if (f_extention != "ini" && f_extention != "lnk")
                    {
                        String f_type = PrjRootPath + "icons\\mainpage\\file.png";
                        if (s.Contains(".csv") ||
                            s.Contains(".xlsx") ||
                            s.Contains(".xlsm") ||
                            s.Contains(".xls"))
                        {
                            f_type = PrjRootPath + "icons\\mainpage\\excel.png";
                        }
                        else if (s.Contains(".docx") ||
                            s.Contains(".doc") ||
                            s.Contains(".docm") ||
                            s.Contains(".dotm"))
                        {
                            f_type = PrjRootPath + "icons\\mainpage\\word.png";
                        }
                        else if (s.Contains(".pptx") ||
                            s.Contains(".pptm") ||
                            s.Contains(".ppt") ||
                            s.Contains(".potm"))
                        {
                            f_type = PrjRootPath + "icons\\mainpage\\powerpoint.png";
                        }
                        else if (s.Contains(".msg"))
                        {
                            f_type = PrjRootPath + "icons\\mainpage\\outlook.png";
                        }
                        else if (s.Contains(".pdf"))
                        {
                            f_type = PrjRootPath + "icons\\mainpage\\pdf.png";
                        }
                        else if (s.Contains(".png"))
                        {
                            f_type = PrjRootPath + "icons\\mainpage\\image.png";
                        }
                        else
                        {
                            f_type = PrjRootPath + "icons\\mainpage\\file.png";
                        }

                        TreeViewItem subitem = new TreeViewItem();
                        subitem.Header = s.Substring(s.LastIndexOf("\\") + 1);
                        subitem.Tag = new object[] { f_type, s };
                        subitem.FontWeight = FontWeights.Normal;
                        item.Items.Add(subitem);
                    } 
                }
            }
            catch (Exception e2) { MessageBox.Show(e2.Message + " " + e2.InnerException); }
        }
    }

Can you guys please send me some examples?你们可以给我一些例子吗? Thank you so much!太感谢了!

First things first, you're not currently using WPF in the way in which it was designed to be used.首先,您当前没有按照设计使用的方式使用 WPF。 Specifically, you're creating your tree elements manually instead of using data binding.具体来说,您正在手动创建树元素,而不是使用数据绑定。 This has come back to bite you now, and it will only get worse if you continue down the path you're on.现在这又回来咬你了,如果你继续沿着你走的路走下去,情况只会变得更糟。

Either way, what you need to do here is template out the checkbox control so that you can change the tick graphic.无论哪种方式,您需要在这里做的是将复选框控件模板化,以便您可以更改刻度图形。 If you add a CheckBox anywhere in your XAML code, place the cursor over it and then in the "Properties" panel on the right select Miscellaneous -> Template -> Convert to New Resource, you'll get a fully expanded CheckBox template (eg "CheckBoxTemplate1") which you can then assign to the CheckBox in your TreeView data template:如果您在 XAML 代码中的任何位置添加 CheckBox,请将光标放在它上面,然后在右侧的“属性”面板中选择 Miscellaneous -> Template -> Convert to New Resource,您将获得一个完全展开的 CheckBox 模板(例如“CheckBoxTemplate1”),然后您可以将其分配给 TreeView 数据模板中的 CheckBox:

<StackPanel Orientation="Horizontal">
    <CheckBox Name="MyCheckBox" Template="{DynamicResource CheckBoxTemplate1}"/>

Looking at the template itself, you'll see all the bits responsible for its graphical appearence, including this:查看模板本身,您会看到负责其图形外观的所有位,包括:

<Grid x:Name="markGrid">
    <Path x:Name="optionMark" Data="F1 M 9.97498,1.22334L 4.6983,9.09834L 4.52164,9.09834L 0,5.19331L 1.27664,3.52165L 4.255,6.08833L 8.33331,1.52588e-005L 9.97498,1.22334 Z " Fill="{StaticResource OptionMark.Static.Glyph}" Margin="1" Opacity="0" Stretch="None"/>
    <Rectangle x:Name="indeterminateMark" Fill="{StaticResource OptionMark.Static.Glyph}" Margin="2" Opacity="0"/>

The "optionMark" element is responsible for the tick, so you can turn it into rectangle by simply changing the path markup: “optionMark”元素负责勾选,因此您可以通过简单地更改路径标记将其变成矩形:

<Path x:Name="optionMark" Data="M 0,0L 10,0L 10,10L 0,10L 0,0 Z " Fill="{StaticResource OptionMark.Static.Glyph}" Margin="1" Opacity="0" Stretch="None"/>

Result:结果:

在此处输入图像描述

Now it seems you only want this to be applied to folders, not files, and this is where it gets tricky.现在看来您只希望将其应用于文件夹,而不是文件,这就是它变得棘手的地方。 Your template code needs a way of distinguishing between the two types of data you're passing it (ie folders vs files).您的模板代码需要一种方法来区分您传递给它的两种类型的数据(即文件夹与文件)。 The only place to add this is as a third element in your subitem.Tag array...which is already getting pretty messy now...and use a DataTemplate to select which path to render ie something like this:添加它的唯一地方是作为 subitem.Tag 数组中的第三个元素......现在已经变得非常混乱......并使用 DataTemplate 选择要渲染的路径,即如下所示:

<Path x:Name="optionMark" Fill="{StaticResource OptionMark.Static.Glyph}" Margin="1" Opacity="0" Stretch="None">
    <Path.Style>
        <Style TargetType="Path">
            <Setter Property="Data" Value="F1 M 9.97498,1.22334L 4.6983,9.09834L 4.52164,9.09834L 0,5.19331L 1.27664,3.52165L 4.255,6.08833L 8.33331,1.52588e-005L 9.97498,1.22334 Z" />
            <Style.Triggers>
                <DataTrigger Binding="{RelativeSource={RelativeSource AncestorType=TreeViewItem}, Path=Tag[2]}" Value="True">
                    <Setter Property="Data" Value="M 0,0L 10,0L 10,10L 0,10L 0,0 Z" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Path.Style>
</Path>

Don't take that DataTrigger binding as verbatim btw, I haven't tested it, but you get the idea...you have to use an element in your tag to tell the Path which data to use.顺便说一句,不要将 DataTrigger 绑定视为逐字记录,我还没有对其进行测试,但是您明白了……您必须在标签中使用一个元素来告诉 Path 要使用哪些数据。

There are other ways to do this, eg when you create your TreeViewItem you can assign it the new ControlTemplate there, which you'll have to load from your resources block.还有其他方法可以做到这一点,例如,当您创建 TreeViewItem 时,您可以在那里为其分配新的 ControlTemplate,您必须从资源块中加载它。 Honestly, though, this is only going to get worse.但老实说,这只会变得更糟。 Your best option, IMO, is to get that DataTrigger out of the template and use TreeView in the way it was meant to be used ie create data structures for each type of object you're trying to draw:您最好的选择,IMO,是从模板中取出 DataTrigger 并按照它的预期使用方式使用 TreeView,即为您尝试绘制的每种类型的对象创建数据结构:

public FolderViewModel[] Items { get; } = {
    new FolderViewModel(), new FolderViewModel(), new FolderViewModel(),
};

public class FolderViewModel : FileViewModel
{
    public FileViewModel[] Children{ get; } = {
        new FileViewModel(),
        new FileViewModel(),
        new FileViewModel()
    };

    public FolderViewModel() : base("Folder") { }
}

public class FileViewModel
{
    public string Text { get; set; }

    public FileViewModel(string text = "File") => this.Text = text;
}

Then assign (or bind) Items to your top-level node, and set DataTemplate and HierarchicalDataTemplate, depending on the type of object:然后将 Items 分配(或绑定)到您的顶级节点,并根据对象的类型设置 DataTemplate 和 HierarchicalDataTemplate:

<TreeView Name="MyTreeView" Margin="249,31,20,24" Background="{x:Null}" BorderThickness="0,0,0,0" Cursor="Arrow" ItemsSource="{Binding Items}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:FolderViewModel}" ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <CheckBox Name="MyCheckBox" Template="{DynamicResource CheckBoxTemplate1}"></CheckBox>
                <TextBlock Text="{Binding Text}"/>
            </StackPanel>
        </HierarchicalDataTemplate>
        <DataTemplate DataType="{x:Type local:FileViewModel}">
            <StackPanel Orientation="Horizontal">
                <CheckBox Name="MyCheckBox"></CheckBox>
                <TextBlock Text="{Binding Text}"/>
            </StackPanel>
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

And that's it.就是这样。 Files will use the default CheckBox template with the tick while Folders will be assigned the new one and get a box.文件将使用带有勾选的默认复选框模板,而文件夹将被分配新模板并获得一个框。

Result:结果:

在此处输入图像描述

The following code in xaml can help xaml 中的以下代码可以提供帮助

<Window.Resources>
    <ResourceDictionary>
        <HierarchicalDataTemplate x:Key="CheckBoxItemTemplate" ItemsSource="{Binding Children, Mode=OneTime}">
            <StackPanel Orientation="Horizontal">
                <CheckBox Focusable="False" IsChecked="{Binding IsChecked}" VerticalAlignment="Center" />
                <ContentPresenter Content="{Binding Name, Mode=OneTime}" Margin="2,0" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </ResourceDictionary>
</Window.Resources>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM