如何用列表中的值填充 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
将列表传递到其构造函数中。
通过 ConfigVMs
的 ObservableCollection
感觉像是违反了 separation of concerns。看起来 ConfigVM
包含许多 ScriptVM
不应该关心的其他内容。它也不应该知道如何导航 ConfigVM->ConfigScriptVM->Name
树。
我希望只传递应用程序其他地方维护的 ObservableCollection
名称字符串,或者可能是一些 IScriptNameProvider
接口,再次在有意义的地方实现(无论管理你的 ConfigVM
合集)
唯一的其他方法很糟糕,例如拥有其他 VM 可以访问的静态配置 class。
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
将列表传递到其构造函数中。
通过 ConfigVMs
的 ObservableCollection
感觉像是违反了 separation of concerns。看起来 ConfigVM
包含许多 ScriptVM
不应该关心的其他内容。它也不应该知道如何导航 ConfigVM->ConfigScriptVM->Name
树。
我希望只传递应用程序其他地方维护的 ObservableCollection
名称字符串,或者可能是一些 IScriptNameProvider
接口,再次在有意义的地方实现(无论管理你的 ConfigVM
合集)
唯一的其他方法很糟糕,例如拥有其他 VM 可以访问的静态配置 class。