WPF:以 MVVM 方式绑定 TreeView 分步教程

2023-11-22

请参阅下一篇文章。原来这一题内容已被删除,因为没有任何意义。简而言之,我询问如何以 MVVM 方式使用 XmlDataProvider 将 XML(我在解析 DLL 程序集时错误生成)绑定到 TreeView。但后来我明白这种方法是错误的,我转而生成数据实体模型(只需编写代表我想在树中公开的所有实体的类)而不是 XML。

所以,结果在下一篇文章中。目前我不时更新这篇“文章”,所以F5,和

享受阅读!


介绍

我找到的正确阅读方式this article

这是一个很长的故事,大多数人可以跳过它:)但是那些想要了解问题和解决方案的人必须阅读这一切!

我是 QA,不久前开始负责我点击的产品的自动化。幸运的是,这个自动机不是在某些测试工具中发生,而是在 Visual Studio 中发生,因此它最大限度地接近开发。

对于我们的自动化,我们使用一个由 MbUnit(Gallio 作为运行程序)和 MINT(除 MbUnit 之外,由我们合作的客户编写)组成的框架。 MbUnit 为我们提供了测试装置和测试,而 MINT 添加了额外的更小的层——测试中的操作。例子。夹具称为“FilteringFixture”。它由大量测试组成,例如“TestingFilteringById”或“TestingFilteringWithSpecialChars”等。每个测试都由操作组成,这些操作是我们测试的原子单元。操作示例包括 -“打开应用程序(参数)”、“OpenFilterDialog”等。

我们已经有很多测试,其中包含很多动作,很混乱。他们使用我们 QA 产品的内部 API。此外,我们开始研究一种新的自动化方法 - 通过 Microsoft UI Automation 进行 UI 自动化(对不起,同义反复)。因此,对于管理者来说,某种“出口者”或“报告者”工具的必要性变得非常迫切。

前段时间,我有一个任务是开发一些应用程序,它可以解析 DLL(其中包含所有的装置、测试和操作),并以人类可读的格式(TXT、HTML、CSV、XML、任何其他格式)导出其结构。 )。但是,在那之后,我就去度假了(两周)。

碰巧的是,我的女朋友去她的家人那里直到放假(她也放假了),而我独自一人留在家里。我一直在思考我要做什么(两周),我记得“编写导出器工具任务”以及我计划开始学习 WPF 多久了。因此,我决定在假期期间完成我的任务,并编写一个 WPF 应用程序。当时我听说了一些关于MVVM的事情,我决定使用纯MVVM来实现。

可以用fixrtures等解析DLL的DLL已经写得相当快(大约1-2天)。之后我开始使用 WPF,本文将向您展示它是如何结束的。

我花了假期的大部分时间(几乎 8 天!),试图在我的头脑和代码中整理它,最后,它(几乎)完成了。我女朋友不相信我一直在做的事情,但我有证据!

用伪代码一步步分享我的解决方案,以帮助其他人避免类似的问题。这个答案更像是教程=)(真的吗?)。如果您对从头开始学习 WPF 时最复杂的事情感兴趣,我会说 - 让这一切成为真正的 MVVM 和 f*g TreeView 绑定!

如果您想要一个包含解决方案的存档文件,我可以稍后再给您,就在我做出决定时,我认为这是值得的。有一个限制,我不确定我是否可以共享 MINT.dll,它带来了 Actions,因为它是由我们公司的客户开发的。但我可以删除它,并共享该应用程序,该应用程序只能显示有关夹具和测试的信息,但不能显示有关操作的信息。

夸夸其谈的话。只需一点点 C# / WinForms / HTML 背景并且无需任何实践,我就能够在近 1 周内实现此版本的应用程序(并撰写本文)。所以,不可能也是可能的!就跟我一样放个假,花点时间去WPF学习吧!

分步教程(尚未附加文件)

任务的简短重复:

前段时间,我有一个开发应用程序的任务,它可以解析 DLL(其中包含测试装置、测试方法和操作 - 我们基于单元测试的自动化框架的单元),并以人类可读的格式(TXT 、HTML、CSV、XML、任何其他)。我决定使用 WPF 和纯 MVVM 来实现它(两者对我来说都是绝对的新事物)。对我来说最困难的两个问题是 MVVM 方法本身,然后是 MVVM 绑定到 TreeView 控件。我跳过了关于 MVVM 划分的部分,这是单独文章的主题。以下步骤是关于以 MVVM 方式绑定到 TreeView。

  1. 没那么重要:创建DLL,它可以通过单元测试打开DLL,并使用反射查找fixture、测试方法和动作(更小级别的单元测试,由我们公司编写)。如果您对它是如何完成的感兴趣,请看这里:使用Reflection解析函数/方法内容
  2. DLL:为固定装置、测试和操作(数据模型、实体模型?)创建单独的类。我们将使用它们进行绑定。您应该自己思考,您的树的实体模型是什么。主要思想 - 树的每个级别都应该由适当的类公开,这些属性可以帮助您在树中表示模型(并且理想情况下,将作为模型或模型的一部分出现在 MVVM 中的正确位置)。就我而言,我对实体名称、子级列表和序号感兴趣。序数是一个数字,代表一个实体在DLL内部代码中的顺序。它帮助我在 TreeView 中显示序数,仍然不确定它是正确的方法,但它有效!
public class MintFixutre : IMintEntity
{
    private readonly string _name;
    private readonly int _ordinalNumber;
    private readonly List<MintTest> _tests = new List<MintTest>();
    public MintFixutre(string fixtureName, int ordinalNumber)
    {
        _name = fixtureName;
        if (ordinalNumber <= 0)
            throw new ArgumentException("Ordinal number must begin from 1");
        _ordinalNumber = ordinalNumber;
    }
    public List<MintTest> Tests
    {
        get { return _tests; }
    }
    public string Name { get { return _name; }}
    public bool IsParent { get { return true; }  }
    public int OrdinalNumber { get { return _ordinalNumber; } }
}

public class MintTest : IMintEntity
{
    private readonly string _name;
    private readonly int _ordinalNumber;
    private readonly List<MintAction> _actions = new List<MintAction>();
    public MintTest(string testName, int ordinalNumber)
    {
        if (string.IsNullOrWhiteSpace(testName))
            throw new ArgumentException("Test name cannot be null or space filled");
        _name = testName;
        if (ordinalNumber <= 0)
            throw new ArgumentException("OrdinalNumber must begin from 1");
        _ordinalNumber = ordinalNumber;
    }
    public List<MintAction> Actions
    {
        get { return _actions; }
    }
    public string Name { get { return _name; } }
    public bool IsParent { get { return true; } }
    public int OrdinalNumber { get { return _ordinalNumber; } }
}

public class MintAction : IMintEntity
{
    private readonly string _name;
    private readonly int _ordinalNumber;
    public MintAction(string actionName, int ordinalNumber)
    {
        _name = actionName;
        if (ordinalNumber <= 0)
            throw new ArgumentException("Ordinal numbers must begins from 1");
        _ordinalNumber = ordinalNumber;

    }
    public string Name { get { return _name; } }
    public bool IsParent { get { return false; } }
    public int OrdinalNumber { get { return _ordinalNumber; } }
}

顺便说一句,我还在下面创建了一个接口,它实现了所有实体。这样的界面可以在将来为您提供帮助。仍然不确定,我是否也应该添加到那里Childrens的财产List<IMintEntity>类型,或者类似的东西?

public interface IMintEntity
{
    string Name { get; }
    bool IsParent { get; }
    int OrdinalNumber { get; }
}
  1. DLL——构建数据模型:DLL有一个方法可以打开带有单元测试和枚举数据的DLL。在枚举过程中,它会构建如下所示的数据模型。给出了真实的方法示例,使用了反射核心+ Mono.Reflection.dll,不要与复杂性相混淆。您所需要的一切 - 看看该方法如何填充_fixtures包含实体的列表。
private void ParseDllToEntityModel()
{
    _fixutres = new List<MintFixutre>();

    // enumerating Fixtures
    int f = 1;
    foreach (Type fixture in AssemblyTests.GetTypes().Where(t => t.GetCustomAttributes(typeof(TestFixtureAttribute), false).Length > 0))
    {
        var tempFixture = new MintFixutre(fixture.Name, f);

        // enumerating Test Methods
        int t = 1;
        foreach (var testMethod in fixture.GetMethods().Where(m => m.GetCustomAttributes(typeof(TestAttribute), false).Length > 0))
        {
            // filtering Actions
            var instructions = testMethod.GetInstructions().Where(
                i => i.OpCode.Name.Equals("newobj") && ((ConstructorInfo)i.Operand).DeclaringType.IsSubclassOf(typeof(BaseAction))).ToList();

            var tempTest = new MintTest(testMethod.Name, t);

            // enumerating Actions
            for ( int a = 1; a <= instructions.Count; a++ )
            {
                Instruction action = instructions[a-1];
                string actionName = (action.Operand as ConstructorInfo).DeclaringType.Name;
                var tempAction = new MintAction(actionName, a);
                tempTest.Actions.Add(tempAction);
            }

            tempFixture.Tests.Add(tempTest);
            t++;
        }

        _fixutres.Add(tempFixture);
        f++;
    }
}
  1. DLL: 公共财产Fixtures of the List<MintFixutre>创建类型以返回刚刚创建的数据模型(夹具列表,其中包含测试列表,其中包含操作列表)。这将是我们的绑定源TreeView.
public List<MintFixutre> Fixtures
{
    get { return _fixtures; }
}
  1. MainWindow的ViewModel(里面有TreeView):包含 DLL 中的对象/类,可以解析单元测试 DLL。还暴露了FixturesDLL 中的公共属性List<MintFixutre>类型。我们将从 MainWindow 的 XAML 绑定到它。类似的东西(简化):
var _exporter = MySuperDllReaderExporterClass ();
// public property of ViewModel for TreeView, which returns property from #4
public List<MintFixture> Fixtures { get { return _exporter.Fixtures; }}
// Initializing exporter class, ParseDllToEntityModel() is called inside getter 
// (from step #3). Cool, we have entity model for binding.
_exporter.PathToDll = @"open file dialog can help";
// Notifying all those how are bound to the Fixtures property, there are work for them, TreeView, are u listening?
// will be faced later in this article, anticipating events
OnPropertyChanged("Fixtures");
  1. MainWindow 的 XAML - 设置数据模板:在包含 TreeView 的 Grid 内,我们创建<Grid.Resources>部分,其中包含我们的一组模板TreeViewItems. HierarchicalDataTemplate(固定装置和测试)用于那些有儿童物品的人,并且DataTemplate用于“叶”项(操作)。对于每个模板,我们指定其内容(文本、TreeViewItem 图像等)、ItemsSource(如果该项目有子项,例如对于 Fixtures,它是{Binding Path=Tests})和 ItemTemplate (同样,只有在该项目有子项的情况下,这里我们设置模板之间的链接 - FixtureTemplate 使用 TestTemplate 作为其子项,TestTemplate 使用 ActionTemplate 作为其子项,Action 模板不使用任何内容,它是叶子!)。重要提示:不要忘记,为了将“一个”模板“链接”到“另一个”模板,“另一个”模板必须在 XAML 中定义在“一个”之上! (只是列举我自己的错误:))

  2. XAML - TreeView 链接:我们设置 TreeView 的方法是:与 ViewModel 中的数据模型(还记得公共属性吗?)以及刚刚准备好的模板链接,这些模板表示内容、外观、数据源和树项的嵌套!还有一个重要的注意事项。不要将 ViewModel 定义为 XAML 中的“静态”资源,例如<Window.Resources><MyViewModel x:Key="DontUseMeForThat" /></Window.Resources>。如果您这样做,那么您将无法在属性更改时通知它。为什么?静态资源就是静态资源,它初始化了,之后就剩下了不可变的。我在这里可能是错的,但这是我的错误之一。所以对于 TreeView 使用ItemsSource="{Binding Fixtures}"代替ItemsSource="{StaticResource myStaticViewModel}"

  3. ViewModel - ViewModelBase - 属性已更改: 几乎全部。停止!当用户打开应用程序时,最初 TreeView 当然是空的,因为用户还没有打开任何 DLL!我们必须等到用户打开一个DLL,然后才执行绑定。它是通过以下方式完成的OnPropertyChanged事件。为了让生活更轻松,我所有的 ViewModel 都是从 ViewModelBase 继承的,它可以将此功能公开给我的所有 ViewModel。

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, args);
    }        
}
  1. XAML - OnPropertyChanged 和命令。用户单击按钮打开包含单元测试数据的 DLL。当我们使用MVVM,然后通过命令处理单击。结束时OpenDllExecuted处理程序OnPropertyChanged("Fixtures")被执行,通知树,它所绑定到的属性已经改变,现在是时候刷新它自己了。RelayCommand辅助类可以取自there)。顺便说一句,据我所知,存在一些辅助库和工具包,XAML 中会发生类似的情况:

  2. 和 ViewModel - 指挥

private ICommand _openDllCommand;

        //...

public ICommand OpenDllCommand
{
    get { return _openDllCommand ?? (_openDllCommand = new RelayCommand(OpenDllExecuted, OpenDllCanExecute)); }
}

        //...

// decides, when the <OpenDll> button is enabled or not
private bool OpenDllCanExecute(object obj)
{
    return true; // always true for Open DLL button
}

        //...

// in fact, handler
private void OpenDllExecuted(object obj)
{
    var openDlg = new OpenFileDialog { ... };
    _pathToDll = openDlg.FileName;
    _exporter.PathToDll = _pathToDll;
                // Notifying TreeView via binding that the property <Fixtures> has been changed,
                // thereby forcing the tree to refresh itself
    OnPropertyChanged("Fixtures");
}
  1. Final UI(但对我来说还不是最终的,还有很多事情要做!)。扩展的 WPF 工具包在某处使用:http://wpftoolkit.codeplex.com/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

WPF:以 MVVM 方式绑定 TreeView 分步教程 的相关文章

随机推荐

  • SQLAlchemy:将查询结果插入到另一个表中

    所以我得到了一些结果install表 像这样 install metadata tables install results session query install
  • Activiti / Camunda 用变量改变边界计时器

    我有一个关于 Activiti Camunda 中用户任务的计时器边界事件的特殊问题 启动流程时 我使用流程变量设置计时器持续时间 并使用边界定义中的表达式来解析该变量 边界事件是在用户任务上定义的
  • Javascript - .innerHTML 更改自动关闭标签

    我正在尝试使用 Javascript 动态地将元素放入其他元素中 而无需刷新页面 它的 AJAX 部分可以工作并且功能正常 然而 由于某种未知的原因 我的代码自动关闭 这是代码片段 您可以看到它实际上并未关闭 但是在浏览器中运行代码后它被关
  • 使用 iOS 的 ExtAudioFileWrite 将音频样本缓冲区写入 aac 文件

    更新 我已经弄清楚了这一点并发布了我的解决方案作为我自己的问题的答案 如下 我正在尝试使用 AAC 格式的 ExtAudioFileWrite 将简单的音频样本缓冲区写入文件 我已经通过下面的代码实现了这一点 将单声道缓冲区写入 wav 文
  • 如何向 HTTP 客户端传递客户端证书?

    我想在服务 A 和 B 之间使用相互 SSL 身份验证 我目前正在使用 Java 实现从服务 A 传递客户端证书 我正在使用 Apache DefaultHttpClient 来执行我的请求 我能够从内部凭证管理器检索服务 A 的客户端证书
  • 我可以使用 CGAffineTransform Rotation 将视图旋转超过 360 度吗?

    我正在编写一个 iPhone 应用程序 并且我有一张图像 我想将其向外旋转 目前我的代码如下所示 包装在 beginAnimations commitAnimations 块中 scale CGAffineTransformScale CG
  • 经典 ASP - ADO 执行传递参数的存储过程

    我需要使用经典 ASP 将参数传递到存储过程中 我确实看到有些人使用 Command 对象 而其他人则不使用它 我的存储过程参数是这样的 RECORD NUMBER decimal 18 0 ErrorType nvarchar 100 I
  • 如何让 Cobertura 因代码覆盖率低而导致 M2 构建失败

    如果行或分支覆盖率低于给定阈值 我正在尝试将 WAR 项目构建配置为失败 我一直在使用这本优秀书籍第455页提供的配置Java电动工具 但没有成功 这是我的项目 Maven 2 POM 的相关片段
  • 从长度为 N 的数组中返回前 k 个值的最佳算法

    我有一个包含 n 个浮点的数组 我希望返回前 k 个 在我的例子中 n 100 k 10 该问题是否有已知的最佳解决路径 谁能提供一个C算法吗 编辑 实际上这里有两个问题 排序和未排序 我对未排序感兴趣 这应该更快 Method 1 由于k
  • 计算给定角度和长度的向量

    有没有什么办法 在javascript中 我可以调用一个带有x和y坐标以及方向 以度为单位的角度 的函数 并且它将返回一组已 移动 10px的新坐标按照原始坐标给出的方向 我环顾四周 但我所能找到的只是获得两个给定坐标的角度的方法 该函数返
  • 如何在 Firebase Firestore Android 发生更改时实现通知?

    有两种类型的应用程序 一种由用户使用 另一种由我 即所有者 使用 因此 每当任何用户将内容 数据 添加到 Cloud Firestore 数据库时 我都希望收到通知 简而言之 如何在 Cloud Firestore 数据库发生更改时发送通知
  • 覆盖maven中第三方jar的依赖

    像这样org carrot2取决于commons httpclient 3 1那么我该如何改变这个commons httpclient 3 1 to HttpClient 4 1 1 我正在日食中工作 正如我想删除的commons http
  • PHP 闭包作用域问题

    显然 pid 超出了这里的范围 难道它不应该与函数一起 关闭 吗 我相当确定这就是闭包在 javascript 中的工作原理 根据一些文章php 闭包被破坏 所以我无法访问this 那么怎样才能 pid可以从这个闭包函数访问吗 class
  • 有没有可以模拟不稳定网络连接的程序? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 目前不接受答案 我们需要模拟不稳定的网络连接 以尝试调试服务器 客户端应用程序中的一些连接问题 我想知道是否有任何程序可以模拟这些条件 例如在微弱的无线网络上 我指
  • 如何在命令行工具中获取 Apple Swift 语言的用户输入? [复制]

    这个问题在这里已经有答案了 我是 Apple 编程新手 我想尝试一下 Swift 但我不知道如何获取用户输入并将其存储在变量中 我正在寻找最简单的方法来做到这一点 比如Python中的input 和raw input Something l
  • 在 TDD 中,为什么选择 OpenEJB,为什么选择 Arquillian?

    我是一名 Web 开发人员 最终参与了一些 Java EE 开发 Richfaces Seam 2 EJB 3 1 JPA 为了测试 JPA 我使用 hypersonic 和 Mockito 但我缺乏更深入的 EJB 知识 有些人可能会说我
  • 第 0 行:解析错误:无法读取未定义的属性“map”

    目前在我的客户端启动服务器 上面的错误就是我遇到的问题 得到 我正在使用 TypeScript ReactJS ESLint 自从这个错误以来我似乎无法继续 一直困扰着我 这ESLint 的 GitHub 页面也没有多大帮助 在我创建 us
  • 在 React Native 中检测 WebView 中的按钮单击

    谁能帮助我如何检测 React Native 中的 Webview 按钮单击事件 如下面的代码所示 我的 WebView index html 中有一个 Button 我想从 React Native 检测单击事件并执行 onClick 方
  • 从脚本标签中提取 src 属性并根据特定匹配进行解析

    因此 我必须使用 JavaScript 确定专有 CRM 中的页面类型 确定页面类型 即前端唯一一致的差异 的唯一方法是检查 src 属性以 modules 开头的脚本标记 在许多列表中 在页眉中十几个脚本标签的列表中 每个页面都有一行以下
  • WPF:以 MVVM 方式绑定 TreeView 分步教程

    请参阅下一篇文章 原来这一题内容已被删除 因为没有任何意义 简而言之 我询问如何以 MVVM 方式使用 XmlDataProvider 将 XML 我在解析 DLL 程序集时错误生成 绑定到 TreeView 但后来我明白这种方法是错误的