1

我正在寻找一种方法来为我的 C# .NET windows 应用程序中的不同类型的控件设置我自己的默认属性值。默认属性值应该“覆盖”控件的现有默认值,但仍然可以通过在设计器中显式设置属性值来“覆盖”。

这是为了简化在客户(或我自己)第 10 次改变主意时更改控件的默认外观/行为的过程。这尤其DataGridView与需要维护大量与布局相关的属性的控件或第 3 方控件有关。

我知道创建继承控件和使用DefaultValue属性的能力,但这不是我正在寻找的解决方案,原因有几个:

  • 必须继承我想要为其指定自定义属性的每种类型的控件很麻烦,更不用说覆盖/隐藏属性和设置 DefaultValue 属性了。
  • 我不能再使用标准的 .NET 控件,而必须使用继承的控件。
  • 继承控件的数量会随着时间的推移而增加,并使工具箱变得杂乱无章。
  • 我自己或项目中的其他开发人员在匆忙时忘记使用新的继承类型,导致控件的行为/外观不一致。

这就是我想象它会起作用的方式:

  • 示例 1:默认情况下ADataGridView具有背景颜色 SystemColors.Window。我将自己的默认值设置为Color.Blue(多么离谱!)。在设计器中,使用默认的背景颜色,即背景颜色没有在 .designer.cs 文件中明确设置。运行应用程序时,会执行一部分代码,导致网格变为蓝色,正如我指定的那样。

  • 示例2:DataGridView在设计器中设置 相同的背景颜色Color.Red。这会覆盖我自己的默认蓝色值,在设计时和运行时都在网格中显示红色背景。


解决方案

正如Daniel Brückner所建议的那样,我的解决方案是使用反射来检查DefaultValue属性。

我递归遍历表单上的所有控件,调用SetDefaultValues每个控件。对于要设置的每个属性值,我调用该SetValue方法,以确保仅设置未更改其默认值的属性。

但是,这种方法存在一个缺陷。已在设计器中显式设置但与其默认值没有差异的属性将被该SetValue方法覆盖。

void SetDefaultValues(Control control)
{
  if (control is DataGridView)
  {
    SetValue(control, "BackColor", Color.Blue);
  }
  else if (control is TextBox)
  {
    // etc.
  }
}

private static void SetValue(object control, string propertyName, object newValue)
{
  System.Reflection.PropertyInfo prop = control.GetType().GetProperty(propertyName);
  if (prop == null)
  {
    throw new ArgumentException(string.Format(
      "Specified property \"{0}\" does not exist on type \"{1}\".", prop.Name, control.GetType().FullName),
      "propertyName");
  }

  bool defaultValueFound = false;
  object defaultValue = null;
  foreach (object attr in prop.GetCustomAttributes(true))
  {
    if (attr is DefaultValueAttribute)
    {
      defaultValue = ((DefaultValueAttribute)attr).Value;
      defaultValueFound = true;
      break;
    }
  }

  if (!defaultValueFound && prop.PropertyType.IsValueType)
  {
    // Get default value for value types if no default value was specified by attributes:
    defaultValue = Activator.CreateInstance(prop.PropertyType);
  }
  if (defaultValue == null || defaultValue.Equals(prop.GetValue(control, null)))
  {
    // If default value matches current value, set new value:
    prop.SetValue(control, newValue, null);
  }
}
4

3 回答 3

3

虽然不像泛型那么漂亮,但您也许可以使用Control Builders做一些事情来实现这一点。

编辑:

昨晚我用 ControlBuilder 做了一个通用包装控件的快速原型。我对结果不满意。虽然您可能可以让它工作,但我相信一个新的 Page 或 Container 类可能是一个更简单的结果。我在测试中使用的源代码可在我的博客上找到。

于 2009-08-28T13:16:54.997 回答
2

我已经使用过或我能想到的几种解决方案。

  1. 继承控制权,但你已经提到了。
  2. 一些更高级的控件库(如 DevExpress)具有从配置文件(在 DevExpress 的情况下为 XML)加载布局的内置能力,甚至完全可换肤(对于 DevExpress 也是如此)。
  3. 有时我为控件创建扩展方法并在用户控件或表单的构造函数中调用它们。这是启用或禁用排序等功能集的简单方法。多选或数据网格中的列重新排序,并提供一致的行为和外观。
  4. 使用数据绑定并将属性绑定到一些配置数据。我相信甚至有一个内置功能——用户设置或类似的东西——但我从未使用过这个功能。
  5. 在大型项目中,像上面提议的那样在所有控件上调用扩展方法并不是很方便。您可以在创建表单时递归访问表单中的所有控件,查看属性,与默认值进行比较(使用反射获取 DefaultValue 属性),如果它们不匹配(即该值已在设计师)从某个文件或内存存储中加载您的默认值并应用它。
于 2009-08-28T13:16:41.877 回答
1

您可以覆盖页面并循环遍历所有控件,例如

foreach (Control c in Page.Controls)
{
   if (c is Textbox)
   {
       (Textbox)c.Color.blah.blah.blah ;)
   }
   ///etc
   Recurse through (c.Controls);
}
于 2009-08-28T13:09:43.220 回答