请参阅下一篇文章。这个原始的一个问题内容已被删除,没有任何意义。简单地说,我问过如何使用MVVM方式的XmlDataProvider将XML(我在解析DLL程序集时错误生成)绑定到TreeView。但后来我才明白这种方法是错误的,我转而使用数据实体模型(只编写表示我希望在树中公开的所有实体的类)而不是XML。
那么,结果在下一篇文章中。目前我不时更新这篇“文章”,所以F5和
享受阅读!
答案 0 :(得分:24)
我找到了阅读this文章的正确方法
这是一个很长的故事,大多数人都可以跳过它:)但那些想要理解问题和解决方案的人必须阅读这一切!
我是QA,前段时间对我点击的产品的自动化负责。幸运的是,这个自动机不是在某些测试工具中进行的,而是在Visual Studio中进行的,因此它最接近开发。
对于我们的自动化,我们使用一个框架,该框架由MbUnit(Gallio作为跑步者)和MINT(MbUnit的添加,由我们合作的客户编写)组成。 MbUnit为我们提供了测试夹具和测试,MINT增加了更小的层 - 测试中的动作。例。 Fixture被称为' FilteringFixture'。它由大量的测试组成,例如' TestingFilteringById'或者测试过滤和特殊字样等等。每个测试都包含动作,这些动作是我们测试的原子单元。操作示例包括 - '打开应用(参数)',' OpenFilterDialog'等
我们已经进行了很多测试,其中包含很多动作,这很麻烦。他们使用我们QA产品的内部API。此外,我们开始研究一种新的自动化方法 - 通过Microsoft UI Automation进行UI自动化(对于重言式抱歉)。因此,某些"出口商"或者"记者"的必要性管理人员变得严厉。
前段时间我有一个开发一些应用程序的任务,它可以解析一个DLL(包含所有的装置,测试和动作),并以人类可读的格式导出其结构(TXT,HTML,CSV,XML) , 任何其他)。但是,在那之后,我去度假(2周)。
事情发生了,我的女朋友去了她的家人,直到度假(她也得到了),我一直待在家里。想想我要做的所有这些时间(2周),我记得那个"写出口工具任务"我计划多久开始学习WPF。所以,我决定在休假期间完成我的任务,并为WPF打扮一个应用程序。那时我听说过MVVM,我决定用纯MVVM实现它。
可以使用fixrtures等解析DLL的DLL写得相当快(〜1-2天)。之后我开始使用WPF,本文将向您展示它是如何结束的。
我度过了我假期的大部分时间(差不多8天!),试图在我的头脑和代码中进行整理,最后,它完成了(差不多)。我的女朋友不会相信我一直在做什么,但我有证据!
在伪代码中逐步共享我的解决方案,以帮助其他人避免类似的问题。这个答案更像是教程=)(真的吗?)。如果您对从头开始学习WPF时最复杂的事情感兴趣,我会说 - 让它完全是MVVM和f * g TreeView绑定!
如果你想要一个带有解决方案的存档文件,我可以稍后给它,就在我作出决定时,它是值得的。一个限制,我不确定我可能会共享带来Actions的MINT.dll,因为它是由我们公司的客户开发的。但我可以删除它,并共享应用程序,它只能显示有关Fixtures和Tests的信息,但不会显示有关操作的信息。
夸夸其谈的话。只需要一点C#/ WinForms / HTML背景并且没有练习,我已经能够在近一周内实现这个版本的应用程序(并撰写本文)。所以,不可能!和我一样度假,把它花在WPF学习上!
重复执行任务:
前段时间我有一个开发应用程序的任务,它可以解析DLL(包含测试夹具,测试方法和操作 - 我们基于单元测试的自动化框架的单元),并将其结构导出为人类可读的格式(TXT,HTML,CSV,XML,任何其他)。我决定使用WPF和纯MVVM来实现它(两者对我来说都是新的东西)。对我来说,最困难的两个问题是MVVM方法本身,然后MVVM绑定到TreeView控件。我跳过关于MVVM部门的部分,它是单独文章的主题。以下步骤是关于以MVVM方式绑定到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; } }
}
BTW,我还在下面创建了一个实现所有实体的接口。这样的界面可以在将来帮助您。仍然不确定,我是否还要添加Childrens
类型的List<IMintEntity>
属性,或类似的东西?
public interface IMintEntity
{
string Name { get; }
bool IsParent { get; }
int OrdinalNumber { get; }
}
_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++;
}
}
Fixtures
类型的公共属性List<MintFixutre>
以返回刚刚创建的数据模型(Fixtures列表,其中包含测试列表,其中包含行动)。这将是TreeView
。public List<MintFixutre> Fixtures
{
get { return _fixtures; }
}
Fixtures
类型的DLL公开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");
MainWindow的XAML - 设置数据模板:在包含TreeView的网格内,我们创建了<Grid.Resources>
部分,其中包含一组我们{{1}的模板}秒。 TreeViewItem
(灯具和测试)用于有子项目的人,HierarchicalDataTemplate
用于&#34; leaf&#34;项目(行动)。对于每个模板,我们指定其内容(文本,TreeViewItem图像等),ItemsSource(如果此项具有子项,例如对于Fixtures,它是DataTemplate
)和ItemTemplate(再次,仅在这种情况下, item有子项,这里我们设置模板之间的链接 - FixtureTemplate为其子项使用TestTemplate,TestTemplate为其子项使用ActionTemplate,Action模板不使用任何东西,它是一个叶子!)。重要提示:不要忘记,为了#34;链接&#34; &#34;一个&#34;模板到&#34;另一个&#34;,&#34;另一个&#34;模板必须在&#34;一个&#34;上面的XAML中定义! (只是列举我自己的错误:))
XAML - TreeView链接:我们使用以下方法设置TreeView:使用ViewModel(请记住公共属性?)和刚刚准备好的模板链接,这些模板代表内容,外观,数据源和嵌套树项目!一个更重要的说明。不要将您的ViewModel定义为&#34;静态&#34; XAML中的资源,如{Binding Path=Tests}
。如果您这样做,那么您将无法在更改的财产上通知它。为什么?静态资源是静态资源,它初始化一个,之后仍然是不可变的。我可能在这里错了,但这是我的错误之一。因此,对于TreeView,请使用<Window.Resources><MyViewModel x:Key="DontUseMeForThat" /></Window.Resources>
而不是ItemsSource="{Binding Fixtures}"
ViewModel - ViewModelBase - 属性已更改:几乎全部。停止!当用户打开一个应用程序时,最初TreeView当然是空的,因为用户还没有打开任何DLL!我们必须等到用户打开DLL,然后才执行绑定。它是通过ItemsSource="{StaticResource myStaticViewModel}"
事件完成的。为了让生活更轻松,我的所有ViewModel都继承自ViewModelBase,它将此功能公开给我的所有ViewModel。
OnPropertyChanged
XAML - OnPropertyChanged和命令。用户单击按钮以打开包含单元测试数据的DLL。当我们使用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);
}
}
时,点击会通过命令来处理。在执行MVVM
处理程序OpenDllExecuted
的末尾,通知树,它所绑定的属性已被更改,现在是时候刷新自己了。 OnPropertyChanged("Fixtures")
辅助类可以从there获取。 BTW,据我所知,有一些帮助库和工具包存在XAML中发生的事情:
和ViewModel - 指挥
RelayCommand