【问题标题】:Bind two UI elements of two dynamically loaded xaml files绑定两个动态加载的 xaml 文件的两个 UI 元素
【发布时间】:2016-01-20 14:32:05
【问题描述】:

我仍在努力寻找更好的问题表述。这是我目前所拥有的:

包含模块列表的自定义类,其中一些模块是通用模块的专用版本

[Serializable]
class ModuleList
{
    public ObservableCollection<Module> Items
    {
       get;
       set;
    }

    public ModuleList()
    { 
       Items = new ObservableCollection<Module>();
    }
}

[Serializable]
class Module : INotifyPropertyChanged
{
   private string name;
   public string Name
   {
      get
      {
         return this.name;
      }
      set
      {
         if(this.name != value)
         {
            this.name = value;
            this.NotifyPropertyChanged("Name");
         }
      }
   }
   [field: NonSerialized]
   public event PropertyChangedEventHandler PropertyChanged;

   public void NotifyPropertyChanged(string propName)
   {
      if(this.PropertyChanged != null)
      {
         this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
      }
   }
}

[Serializable]
class SpecializedModule : Module
{
   public SpecializedModule()
   {
      mode = false;
   }

   private bool mode;
   public bool Mode
   {
      get
      {
         return this.mode;
      }
      set
      {
         if(this.mode != value)
         {
            this.mode = value;
            this.NotifyPropertyChanged("Mode");
         }
      }
   }
}

项目列表作为ItemSource 用于ListView。选择列表中的项目使用XamlReader 加载相应的.xaml。 System.Windows.Control.Grid,用作特定于模块的 UI 的容器。通过将网格的数据上下文设置为模块列表,“正常”绑定按预期工作

switch (it.ModID)
{
   case "SOMEMODID":
      loadGrid("somemodule.xaml");
      break;
   default:
      loadGrid("_dummy.xaml");
      break;
}
grpModCfg.DataContext = modListSel;

然后使用转换器将子网格的DataContext 设置为相应的模块

public class ModuleListToModuleConverter : System.Windows.Data.IValueConverter
   {
      public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
      {
         if (value is ModuleList)
         {
            ModuleList modList = value as ModuleList;

            int idx = modList.getItemIndex(parameter as string);
            if (idx != -1)
            {
               return modList.Items[idx];
            }
            else
            {
               return null;
            }
         }
         else
         {
            return null;
         }
      }

      public object ConvertBack(object obj, Type targetType, object parameter, System.Globalization.CultureInfo culture)
      {
         return null;
      }
   }

.xaml 示例:

<Grid Name="grdSomeMod" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:local="clr-namespace:ModCfg;assembly=ModCfg" 
      mc:Ignorable="d" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      d:DesignHeight="395" 
      d:DesignWidth="480" >
   <Grid.Resources>
      <local:ModuleListToModuleConverter x:Key="modlistConverter" />
   </Grid.Resources>
   <Grid.DataContext>
      <Binding Converter="{StaticResource modlistConverter}" ConverterParameter="SOMEMODEID" />
   </Grid.DataContext>
<Grid.ColumnDefinitions>
      <ColumnDefinition Width="240*" />
      <ColumnDefinition Width="240*" />
   </Grid.ColumnDefinitions>
   <CheckBox Content="Some Parameter" Height="16" HorizontalAlignment="Left" IsChecked="{Binding PropertyOfSomeModule}" Margin="6,6,0,0" VerticalAlignment="Top" Name="chkSomePar" />
</Grid>

这适用于绑定特定模块的属性。但是,例如,我也依赖于其他模块的属性,所以我需要类似

  • CheckBoxA.IsChecked = moduleA.SomeProperty
  • CheckBoxB.IsEnabled = moduleB 在列表中 && moduleB.SomeOtherProperty == true/false

到目前为止我尝试了什么:

  • 方法 A
    • DataContext 设置保留到我的模块列表中,以便我可以访问所有这些
    • 为每个元素设置上下文,以便那些需要访问另一个模块的人拥有上下文
    • 问题:一旦我将上下文设置到一个模块,我就无法访问另一个模块,因此将IsEnabled绑定到模块A的属性和IsChecked绑定到模块B的属性是不可能的。
  • 方法 B
    • 使用带参数的转换器指定要搜索的模块和属性(逗号分隔,然后在转换器中拆分)
    • 问题:它适用于 IsEnabled,但 IsChecked 抱怨由于双向绑定需要路径,我似乎无法找到一个有效的路径。

方法 A 的 XAML:

<Grid Name="grdSomeMod" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:local="clr-namespace:ModCfg;assembly=ModCfg" 
      mc:Ignorable="d" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      d:DesignHeight="395" 
      d:DesignWidth="480" >
   <Grid.Resources>
      <local:ModuleListToModuleConverter x:Key="modlistConverter" />
   </Grid.Resources>

   <Grid.ColumnDefinitions>
      <ColumnDefinition Width="240*" />
      <ColumnDefinition Width="240*" />
   </Grid.ColumnDefinitions>
   <CheckBox Content="Some Parameter" Height="16" HorizontalAlignment="Left" IsChecked="{Binding PropertyOfSomeModule}" Margin="6,6,0,0" VerticalAlignment="Top" Name="chkSomePar>
      <CheckBox.DataContext>
         <Binding Converter="{StaticResource modlistConverter}" ConverterParameter="SOMEMODEID" />
      </Checkbox.DataContext>
   </CheckBox>
</Grid>

方法 B 的 XAML:

<Grid Name="grdSomeMod" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:local="clr-namespace:ModCfg;assembly=ModCfg" 
      mc:Ignorable="d" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      d:DesignHeight="395" 
      d:DesignWidth="480" >
   <Grid.Resources>
      <local:ModuleListToModuleConverter x:Key="modlistConverter" />
      <local:ModuleListToModuleDefineConverter x:Key="defBinaryConverter" />
   </Grid.Resources>
   <Grid.DataContext>
      <Binding Converter="{StaticResource modlistConverter}" ConverterParameter="SOMEMODEID" />
   </Grid.DataContext>
   <Grid.ColumnDefinitions>
      <ColumnDefinition Width="240*" />
      <ColumnDefinition Width="240*" />
   </Grid.ColumnDefinitions>
   <CheckBox Content="Some Parameter" Height="16" HorizontalAlignment="Left" IsChecked="{Binding PropertyOfSomeModule}" Margin="6,6,0,0" VerticalAlignment="Top" Name="chkSomePar">

      <CheckBox.IsChecked>
         <!--not working atm-->
         <Binding Path="." Converter="{StaticResource defBinaryConverter}" ConverterParameter="SOMEMODID,SomeProperty"  />
      </CheckBox.IsChecked>
      <CheckBox.IsEnabled>
         <Binding Converter="{StaticResource defBinaryConverter}" ConverterParameter="SOMEOTHERMODID,SomeOtherProperty"  />
      </CheckBox.IsEnabled>
   </CheckBox>
</Grid>

以及方法 B 的转换器:

public class ModuleListToModuleDefineConverter : System.Windows.Data.IValueConverter
{
   public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   {
      if (value is ModuleList)
      {
         ModuleList modlist = value as ModuleList;
         string paramList = parameter as string;
         string[] pars = paramList.Split(',');
         int idx = modlist.getItemIndex(pars[0]);
         if (idx != -1)
         {
            switch (pars[0])
            {
               case "SOMEMODID":
                  switch (pars[1])
                  {
                     case "SomeProperty":
                        return (modlist.Items[idx] as SomeModule).C0x10;
                  }
                  break;
               case "SOMEOTHERMODID":
                  switch (pars[1])
                  {
                     case "SomeOtherProperty":
                        return (modlist.Items[idx] as SomeOtherMod).SomeOtherProperty;
                  }
                  break;
            }
         }
         return false;
      }
      else
      {
         return false;
      }
   }

   public object ConvertBack(object obj, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   {
      return null;
   }
}

这是相当多的代码,但这也是我已经尝试解决了大约一周的问题,我觉得我已经非常接近了,但可能会忽略一些东西。

也许有更优雅的方式来做到这一点?

【问题讨论】:

  • 我觉得这个问题不清楚。例如。为什么要动态加载 XAML?为什么不将适当的模板作为您根据需要引用的资源?为什么数据上下文不仅仅是加载/引用模板的模块实例?上面有一个很大的代码转储,但它不完整,同时包含了似乎对这个问题不是绝对必要的东西。请阅读minimal reproducible example 以获取有关如何提供良好代码示例的建议。请具体说明代码的作用以及与您想要的有何不同。
  • @PeterDuniho 我应该说我对 WPF 也很陌生,所以我的问题可能仅仅是因为我对此事缺乏了解。动态加载 XAML 似乎是没有一个包含所有模块的大型 XAML 的最佳方法。在我的情况下,我看不出从资源加载页面与从文件加载有何不同。如果每个模块的数据上下文从一开始就设置为模块,那么据我所知,无法访问另一个模块的属性来映射依赖关系 - 这是我的问题。
  • “在我的情况下,我看不出从资源加载页面与从文件加载有何不同” -- 如果您已将模板定义为资源,那么您可以利用 WPF 中现有的模板功能,而不必编写代码隐藏来匹配模板与数据类型。至于其余的,不幸的是,您的问题并不清楚“访问另一个模块的属性以映射依赖项”是什么意思。模板应该只需要访问为其定义的模块类型的属性。
  • @PeterDuniho:感谢有关资源的想法,它大大简化了我的加载。我还意识到我犯了一些其他的逻辑错误,所以我设法想出了一个解决方案。抱歉问题表述不清楚,下次我会尽量说清楚。

标签: c# wpf data-binding


【解决方案1】:

我已经意识到,即使我设法按照我想要的方式在 XAML 中映射我的依赖项,我也无法将它们传递给 C# 代码。例如,即使在 GUI 中禁用了某个功能,我也无法在我的模块对象中知道这一点。所以我的解决方案是为那些具有依赖关系的属性添加一个附加属性,将该属性映射到“IsEnabled”,并在加载 XAML 后从对象内更新值。使用我在我的问题中发布的 sn-ps,我的变化是

[Serializable]
class SpecializedModule : Module
{
   public SpecializedModule()
   {
      mode = false;
   }

   private bool mode;
   public bool Mode
   {
      get
      {
         return this.mode;
      }
      set
      {
         if(this.mode != value)
         {
            this.mode = value;
            this.NotifyPropertyChanged("Mode");
         }
      }

   private bool modeEn;
   public bool ModeEn
   {
      get
      {
         return this.modeEn;
      }
      set
      {
         if(this.modeEn != value)
         {
            this.modeEn = value;
            this.NotifyPropertyChanged("ModeEn");
         }
      }
   }
}

单个 XAML 的数据上下文现在设置为相应的模块,因此绑定很简单。

<CheckBox Content="Some Parameter" Height="16" HorizontalAlignment="Left" IsChecked="{Binding PropertyOfSomeModule}" Margin="6,6,0,0" VerticalAlignment="Top" Name="chkSomePar" IsChecked="{Binding Mode}" IsEnabled="{Binding ModeEn}" />

现在我要做的就是编写一个函数来检查我的功能所依赖的模块是否在列表中,并在用户选择项目时调用它。

switch (it.ModID)
{
   case "SOMEMODID":
      loadGrid("somemodule.xaml");
      break;
   default:
      loadGrid("_dummy.xaml");
      break;
}
grpModCfg.DataContext = modListSel;
modList.refreshDependencies();

刷新功能看起来像

public void refreshDependencies()
{
   foreach (Module mod in this.Items)
   {
      int idx = -1;
      switch (mod.ModID)
      { 
         case "SOMEMODID":
            // Mode depends on SOMEOTHERMODID
            idx = this.modList.getItemIndex("SOMEOTHERMODID");
            if (idx != -1)
            {
               (mod as SomeModule).ModeEn = (this.Items[idx] as SomeOtherModule).SomeOtherProperty;
            }
            else
            {
               (mod as SomeModule).ModeEn = false;
            }
            break;
      }
   }
}

所以现在我可以访问代码中的所有模块,并且 GUI 反映了对象的状态。

编辑:

我也意识到在代码之外编辑单个 XAML 文件没有任何意义,因为无论如何 C# 代码都必须更改,所以我听从了 Peter Duniho 的建议并切换到加载单个 XAML 文件作为资源。 This MSDN page在这个过程中帮助了我,所以现在代码的加载部分减少到了

loadGrid(it.ModID);
grpModCfg.DataContext = it;
modList.refreshDependencies();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-13
    • 1970-01-01
    • 2016-10-14
    • 1970-01-01
    • 1970-01-01
    • 2010-09-20
    相关资源
    最近更新 更多