如何用列表中的值填充ComboBox,并将所选项目绑定到另一个ViewModel中的字符串 [英] How to fill a ComboBox with values from a list, and bind the selected item to a string in another ViewModel

查看:94
本文介绍了如何用列表中的值填充ComboBox,并将所选项目绑定到另一个ViewModel中的字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

TL; DR位于底部

TL;DR at bottom

有关背景的一些小故事.我正在创建一个xml工具,使人们可以输入我们正在制作的游戏所需的数据,而不必担心直接手工编写XML.该工具将输出供游戏引擎使用的各种XML文件(例如,复杂的NPC对话节点,其中引用了使用给定对话时要运行的脚本的引用……基本上所有数据都用于游戏引擎).

A little back story for context. I'm creating an xml tool that will enable folks to input data needed for a game we're making without having to worry about hand-jobbing XML directly. The tool will output various XML files to be used by the game engine (for example, complex NPC Dialogue nodes with references to scripts to run when a given piece of dialogue is used... basically all the data to feed the game engine).

我一直在努力找出我遇到的一个令人困惑的问题...

I'm stuck trying to figure out a perplexing issue I'm having...

我正在创建一个列表视图(作为名为ActionsBox的用户控件-需要在其上重新使用),以及一个包含ComboBox的数据模板. ComboBox需要从xml配置文件中填充其值(只读,但我使其成为完整的viewmodel以获得恒定性),但是所选项目将需要绑定到单独的viewmodel中的字符串属性(存储实际内容)配置/静态内容).根据我的阅读,由于控件不能绑定到两个不同的东西,因此这实际上是行不通的.

I'm creating a list view (as a user control named ActionsBox - need re-usability on it), with a data template that contains a ComboBox. The ComboBox needs to have its values filled from an xml config file (readonly, but I made it a full viewmodel for constancy), but the selected item will need to be bound to a string property in a separate viewmodel (which store actual content instead of config/static things). From what I've read this won't really work as controls can't be bound to two different things.

此位的目的是限制人们使用该工具仅选择适当的值(即,确保他们仅选择游戏引擎中实际存在的脚本.每次添加一个可以从XML调用引擎的新脚本.)

The purpose of this bit is to constrain people using the tool to only select appropriate values (i.e. make sure they only select scripts that actually exist in the game engine. We'll be updating this config XML file every time we add a new script to the engine that can be called from XML).

我觉得我可能会在体系结构上解决这个错误(但?),但是我真的想不出任何解决该问题的好方法(而Google并不是那么有用).

I have a feeling I might be going about this wrong (over?)architecturally, but can't really think of any good way to solve it (and google isn't being all that helpful).

解决此问题的最佳方法是什么(代码和体系结构详细说明如下)?

What's the best way to tackle this approach (code and architecture detail follows)?

Xml(目标是用脚本名称填充此组合框):

Xml (The goal is to fill this combobox with script names):

<ConditionScripts>
    <Script>
      <Name>Test Script 1</Name>
      <Description>This script does x</Description>
      <Parameters>
        <Parameter>
          <Name>TestStr</Name>
          <Type>string</Type>
          <Description>Blah blah</Description>
        </Parameter>
      </Parameters>
    </Script>
    <Script>
      <Name>Test Script 2</Name>
      <Description>This script does y</Description>
      <Parameters>
        <Parameter>
          <Name>TestStr</Name>
          <Type>string</Type>
          <Description>Blah blah</Description>
        </Parameter>
        <Parameter>
          <Name>TestInt</Name>
          <Type>int</Type>
          <Description>BlahBlah</Description>
        </Parameter>
      </Parameters>
    </Script>
  </ConditionScripts>

配置名称的视图模型:

public class ConfigScriptViewModel : ViewModelBase
{
    #region Properties
    private string name;
    public string Name
    {
        get { return Name; }
        set 
        { 
            name = value;
            OnPropertyChanged();
        }
    }

    private string description;
    public string Description
    {
        get { return description; }
        set
        {
            description = value;
            OnPropertyChanged();
        }
    }

    private ObservableCollection<ConfigScriptParameterViewModel> parameters;
    public ObservableCollection<ConfigScriptParameterViewModel> Parameters
    {
        get { return parameters; }
        set
        {
            parameters = value;
            OnPropertyChanged();
        }
    }
    #endregion

    public ConfigScriptViewModel(XElement scriptNode)
    {
        this.Name = scriptNode.Element("Name").Value;
        this.Description = scriptNode.Element("Description").Value;

        var paramsVm = new ObservableCollection<ConfigScriptParameterViewModel>();

        foreach (var param in scriptNode.Element("Parameters").Elements())
        {
            var paramVm = new ConfigScriptParameterViewModel(param);
            paramsVm.Add(paramVm);
        }

        this.Parameters = paramsVm;
    }
}

以上内容保存在另一个ViewModel-ConfigViewModel中,该文件将它们存储在ObservableCollection中,并且是"Config"分支的最高级别.代码可根据要求提供,但此处暂时省去了空间(因为它只是一个容器VM).

The above is held in another ViewModel - ConfigViewModel which has them stored in an ObservableCollection and is the highest level of the "Config" branch. Code available upon request, but left out here to save space for now (since it's just a container VM).

有一个更高的ViewModel级别,其中包含上述ConfigViewModel的MasterViewModel以及其他更高级别的ViewModel(例如DialoguesViewModel,它将使用多个ActionsBox之一).

There's one ViewModel level higher, the MasterViewModel which contains the aforementioned ConfigViewModel as well as the other high level ViewModels (such as the DialoguesViewModel, which is one place of several the ActionsBox will be used).

更改为保存实际数据的ViewModels分支的齿轮-

Changing gears to the branch of ViewModels that hold actual data -

ScriptViewModel包含ScriptName属性,该属性需要基于组合框的选定项进行设置(并且在初始加载时将组合框设置为该项).最终,这将在一个(不同的)xml文件中结束(各种XML文件是我的模型).

The ScriptViewModel contains the ScriptName property that needs to be set based off the selected item of the combobox (and have the combobox set to that item on initial load). Ultimately this will wind up in an (different) xml file (the various XML files are my model).

ScriptViewModel:

ScriptViewModel:

public class ScriptViewModel : ViewModelBase
{
    private ObservableCollection<ScriptArgumentViewModel> arguments;

    public ObservableCollection<ScriptArgumentViewModel> Arguments
    {
        get { return arguments; }
        set
        {
            arguments = value;
            OnPropertyChanged();
        }
    }

    private string scriptName;

    public string ScriptName
    {
        get { return scriptName; }
        set
        {
            scriptName = value;
            OnPropertyChanged();
        }
    }

    public ScriptViewModel(XElement scriptNode)
    {
        originalModel = scriptNode;
        ScriptName = (string)scriptNode.Attribute("ScriptName");


        var args = new ObservableCollection<ScriptArgumentViewModel>();

        foreach (var arg in scriptNode.Elements(CommonTypesNS + "Argument"))
        {
            var a = new ScriptArgumentViewModel(arg);
            args.Add(a);
        }

        if (args.Count > 0)
        { Arguments = args; }
    }
}

由于一个Action可以具有0个或多个脚本,因此ScriptVM被保存在ObservableCollection的ActionsViewModel中.再次,为了简短起见,省略了该代码,但是如果需要的话可以使用.动作可在其他几个视图模型中重用(它们是可重用的高位).

The ScriptVM(s) are held in an ActionsViewModel in an ObservableCollection, as an Action can have 0 or many scripts. Again, that code is omitted for shortness sake, but is available if needed. Actions are reused throughout several other viewmodels (they're a high re-usable bit).

最后,我们进入了我正在制作的用户控件.

Finally we get to the user control I'm making.

带有ListView XAML的UserControl:

UserControl w/ ListView XAML:

<UserControl x:Class="SavatronixXmlTool.Code.UserControls.ActionsBox"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:stxVm="clr-namespace:SavatronixXmlTool.Code.ViewModels"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<ListView x:Name="ActionsListView">
    <ListView.Resources>
        <!--Script data template-->
        <DataTemplate DataType="{x:Type stxVm:ScriptVM}">
            <StackPanel Orientation="Horizontal">
                <TextBlock>Type: Script</TextBlock>
                <Label>Script Name:</Label>
                <!-- stuck here >.< -->
                <ComboBox />
            </StackPanel>
        </DataTemplate>
    </ListView.Resources>
</ListView>

我认为,因为ActionsBox可以在不同的地方使用,并且继承的数据上下文可能不同,所以我需要进行标准化.因此,我在后面的代码中使用了Dependency Properties来获取ActionsViewModel(并在ActionsBox构造函数中设置DataContext = this;).

I figured since the ActionsBox can be used in different places, with potentially different inherited data contexts, I needed to normalize. So I used Dependency Properties in the code-behind to take an ActionsViewModel (and set DataContext = this; in the ActionsBox constructor).

出于上下文的考虑(以及出于浓厚兴趣的人们),该工具的目标输出XML看起来类似于...

For contexts sake (and for those with piqued interest) the target output XML of the tool will look something like...

                  <DirectDialogue Id="Rachel-DD-12" >
                    <Text>And talking some more!</Text>
                    <Conditions></Conditions>
                    <Actions>
                      <cmn:Script ScriptName="TestScript">
                        <cmn:Argument Name="testInt" Type="int" Value="5"/>
                      </cmn:Script>
                    </Actions>
                    <Voice></Voice>
                    <Animation></Animation>
                    <Notes></Notes>
                  </DirectDialogue>

使用脚本位(以及在不同的xml文件(如Quests)中的动作)节点,对于整个puzzel来说都是高度可重用的部分.

With the script bit(and action, across differing xml files, like Quests) node being a highly reusable piece to the overall puzzel.

TL; DR **** 无论如何,我将不胜感激,以帮助您弄清楚如何从一个来源加载组合框中的值,但将SelectedItem的更改绑定到另一个位置.永远感激不尽.

TL;DR**** Anyway, I'd appreciate any help with figuring out how to load values in a combobox from one source, yet have changes to SelectedItem bound to a different place. Many thanks will be eternally given.

推荐答案

这全部归结为您的ScriptVM需要访问可能的脚本名称列表,这些名称保存在ConfigVMs的集合中.然后,它们可以将它们公开为您的组合可以绑定到的另一个属性.

What this all boils down to is that your ScriptVM needs access to the list of possible script names, which are held deep in your collection of ConfigVMs. It can then expose these as another property that your combo can bind to.

如何执行此操作取决于应用程序的结构.通常倾向于使用依赖注入,这意味着您的ScriptVM会将列表传递到其构造函数中.

How you do this depends on the structure of your app. Dependency injection is generally favored which would mean that your ScriptVM gets the list passed into its constructor.

通过ConfigVMsObservableCollection感觉违反了关注点分离.看来ConfigVM包含了许多其他内容,而ScriptVM都不在乎.它也不必知道如何导航ConfigVM->ConfigScriptVM->Name树.

Passing the ObservableCollection of ConfigVMs feels like a violation of separation of concerns. It looks like ConfigVM holds a lot of other stuff that ScriptVM shouldn't care about. Neither should it have to know how to navigate the ConfigVM->ConfigScriptVM->Name tree.

我希望只传入在应用程序中其他位置维护的名称字符串ObservableCollection,或者可能是某些IScriptNameProvider接口,在有意义的地方再次实现(无论管理您的ConfigVM集合)

I would favour just passing in an ObservableCollection of name strings that is maintained elsewhere in the app, or possibly some IScriptNameProvider interface, again implemented where it makes sense (whatever manages your ConfigVM collection)

仅有的其他方法很讨厌,例如拥有其他VM可以访问的静态配置类.

The only other ways are nasty, such as having a static config class that the other VMs can access.

这篇关于如何用列表中的值填充ComboBox,并将所选项目绑定到另一个ViewModel中的字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆