【问题标题】:Application-wide control defaults应用程序范围的控制默认值
【发布时间】:2010-11-23 17:29:28
【问题描述】:

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

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

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

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

这就是我想象中的工作方式:

  • 示例 1: 默认情况下,DataGridView 具有背景颜色 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);
  }
}

【问题讨论】:

  • 这就是能够在设计师中使用泛型将很棒的地方

标签: c# .net winforms default-value


【解决方案1】:

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

foreach (Control c in Page.Controls)
{
   if (c is Textbox)
   {
       (Textbox)c.Color.blah.blah.blah ;)
   }
   ///etc
   Recurse through (c.Controls);
}

【讨论】:

  • 我自己实现了类似的东西,但是,这并不能解决在设计器中明确设置的运行时不覆盖属性的问题,这是我主要关心的问题之一。
【解决方案2】:

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

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

【讨论】:

  • 我喜欢您使用反射检查 DefaultValue 属性的想法,因为它可能确实有效。我会试试看:)
  • 我已经更新了我的帖子。现在我已经按照你的建议通过检查 DefaultValue 属性来解决它。
【解决方案3】:

虽然没有泛型那么漂亮,但我可以用Control Builders 做一些事情来解决这个问题。

编辑:

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

【讨论】:

  • 在今天之前我什至都不知道有这样的东西存在。虽然我知道在我的 .NET 知识方面总是有更多需要学习和改进的地方,但很难找到一个我什至不知道的早在 2.0 就已经存在的功能!
  • 这听起来很有趣,但我无法判断它是否解决了在运行时忽略显式设置的属性的问题,如我的第二个示例中所述。我会多读一点,但如果您有空时能举个例子,我也将不胜感激。
  • 在快速搜索之前我不知道它。我猜搜索词很幸运:)
  • 感谢 Matthew,我将改为检查 DefaultValueAttribute。
猜你喜欢
  • 1970-01-01
  • 2010-10-29
  • 2023-03-25
  • 2014-01-30
  • 1970-01-01
  • 1970-01-01
  • 2017-11-04
  • 2022-01-22
  • 1970-01-01
相关资源
最近更新 更多