如何用列表中的值填充 ComboBox,并将所选项目绑定到另一个 ViewModel 中的字符串

How to fill a ComboBox with values from a list, and bind the selected item to a string in another ViewModel

TL;底部的 DR

关于上下文的一些背景故事。我正在创建一个 xml 工具,使人们能够输入我们正在制作的游戏所需的数据,而不必担心直接手动操作 XML。该工具将输出各种 XML 文件以供游戏引擎使用(例如,当使用给定的一段对话时,复杂的 NPC 对话节点引用脚本到 运行 ...基本上所有的为游戏引擎提供数据)。

我无法解决我遇到的一个令人困惑的问题...

我正在创建一个列表视图(作为一个名为 ActionsBox 的用户控件 - 需要对其进行重用),以及一个包含 ComboBox 的数据模板。 ComboBox 需要从 xml 配置文件中填充其值(只读,但我将其设为完整的视图模型以保持稳定性),但 selected 项目需要绑定到字符串 属性 在一个单独的视图模型中(它存储实际内容而不是 config/static 东西)。根据我的阅读,这不会真正起作用,因为控件不能绑定到两个不同的东西。

此位的目的是限制使用该工具的人只能使用 select 适当的值(即确保他们只有 select 实际存在于游戏引擎中的脚本。我们将每次我们向引擎添加可以从 XML) 调用的新脚本时更新此配置 XML 文件。

我有一种感觉,我可能会在架构上做错(结束?),但真的想不出任何解决它的好方法(而且 google 并没有那么有用) .

处理这种方法的最佳方法是什么(代码和架构细节如下)?

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

<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" 分支的最高级别。代码可应要求提供,但此处省略以暂时保存 space(因为它只是一个容器 VM)。

还有一个更高级别的 ViewModel,包含上述 ConfigViewModel 的 MasterViewModel 以及其他高级 ViewModel(例如 DialoguesViewModel,这是将使用的几个 ActionsBox 之一)。

换档到保存实际数据的 ViewModels 分支 -

ScriptViewModel 包含需要根据组合框的 selected 项目设置的 ScriptName 属性(并在初始加载时将组合框设置为该项目)。最终这将在一个(不同的)xml 文件中结束(各种 XML 文件是我的模型)。

脚本视图模型:

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

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

终于到了我正在制作的用户控件。

带 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 可以用在不同的地方,可能有不同的继承数据上下文,我需要规范化。因此,我在代码隐藏中使用依赖属性来获取 ActionsViewModel(并在 ActionsBox 构造函数中设置 DataContext = this;)。

出于上下文考虑(以及那些有兴趣的人),该工具的目标输出 XML 将类似于...

                  <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 文件,如任务)节点是整个拼图中高度可重用的部分。

TL;博士**** 无论如何,如果您能帮助我弄清楚如何从一个来源加载组合框中的值,但将 SelectedItem 的更改绑定到不同的位置,我将不胜感激。千恩万谢。

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

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

通过 ConfigVMsObservableCollection 感觉像是违反了 separation of concerns。看起来 ConfigVM 包含许多 ScriptVM 不应该关心的其他内容。它也不应该知道如何导航 ConfigVM->ConfigScriptVM->Name 树。

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

唯一的其他方法很糟糕,例如拥有其他 VM 可以访问的静态配置 class。