在 .Net/Wpf 中实现工具箱的模式(不是视觉部分)?

发布于 08-10 17:41 字数 573 浏览 12 评论 0原文

我正在构建一个 Wpf 应用程序,当前有一个工具箱,其外观和功能与 Visual Studio 工具箱类似。我有一系列在工具箱中表示的类和不同类型的对象,每个对象都可以包含一些(但不是全部)工具箱项目。

作为让我的应用程序正常运行的权宜之计,我将工具箱功能硬编码到用户控件中。现在我必须改进一个没有硬编码的更好的设计。

我有一个简单的 ToolBoxItem 类,它具有类型、标签和图标属性。需要进入工具箱的每个类都被添加到 ToolBoxItems 集合中并正确显示。

但是我正在努力为两个问题提出一个优雅的解决方案:

  • 如何在正在编辑的对象中创建由 ToolBoxItem Type 表示的类的实例。
  • >如何隐藏与正在编辑的项目不兼容的工具箱项目。 我现在使用每个编辑的类必须通过接口(即 CanSupportClassX、CanSupportBaseClassY)实现的硬编码属性来手动执行此操作。

我的感觉是,委托和泛型可能是一个解决方案,但我过去只使用过这些功能,并且不确定如何继续。

I'm building a Wpf app that currently has a toolbox which looks and functions similarly to the Visual Studio toolbox. I have a range of classes that are represented in the toolbox and different types of objects that can each contain some, but not all, of the toolbox items.

As a stopgap measure to get my app up and going, I hardcoded the toolbox functionality into the UserControls. Now I must progress to a better design that isn't hardcoded.

I've got a simple ToolBoxItem class that has properties for Type, Label and Icon. Each class that needs to go into the toolbox is added to a collection of ToolBoxItems and is displaying correctly.

But I am struggling to come up with an elegant solution for two problems:

  • How to create an instance of the class represented by the ToolBoxItem Type in the object being edited.
  • How to hide Toolbox items that are not compatible with the item being edited. I manually do this now with hardcoded properties that each edited class must implement via an interface (ie CanSupportClassX, CanSupportBaseClassY).

My feeling is that delegates and perhaps generics could be a solution but I've only consumed those features in the past and am unsure how to proceed.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

靖瑶2024-08-17 17:41:40

您想要实现的目标的一个很好的例子可以在 AvalonDock 中找到,它是完全开源的。如果您想了解如何将 AvalonDock 与 MVVM 结合使用(即将工具放入 Windows 等),请查看 SoapBox 核心框架

A good example of what you're trying to achieve can be found in AvalonDock, which is completely open source. If you want to see how to use AvalonDock with MVVM (i.e. putting tools in windows and so on), check out the SoapBox Core Framework.

萌能量女王2024-08-17 17:41:40

我认为最好的方法是 Visual Studio 本身采用的方法:每个对象通过其属性的类型来描述它可以包含的内容。

因此,对于 Visual Studio,我可以创建一个可以容纳小部件的新对象,如下所示:

[ContentAttribute("Children")]
public class MyObject
{
  ...
  public ICollection<Widget> Children { get ... set ... }
}

Visual Studio 类型系统会完成其余的工作。

当然,这里存在一些限制,可能需要一些扩展,甚至需要其他技术。例如,如果您的对象可以接受除 object 之外不共享公共基类的两种不同类型的内容,则必须将您的属性声明为 ICollectionICollectioncode> 在这种情况下,除非您有额外的机制,否则您的工具箱将不知道它真正需要什么。

许多附加机制可用于这些特殊情况,例如:

  • 允许多个“内容”属性,例如ICollection; WidgetChildren { 获取...设置...}; ICollectionDoodadChildren { get ... set ... }
  • 创建一个可应用于该类的属性,为其提供可包含的对象类型,例如 [ContentTypeAllowed(typeof(Widget))] [ContentTypeAllowed (typeof(Doodad))] 其中实际内容属性为 IEnumerable
  • 创建一个仅包含类名列表的属性,例如 [ContentTypesAllowed("Widget ,Doodad")]
  • 创建一个方法,如果在目标对象中定义,则评估潜在的内容类,如果它可以是子对象,则返回 truefalse > 如果没有,类似这样 bool CanAcceptChildType(Type childType) { return type.IsSubclassOf(Widget) && !type==typeof(BadWidget); }
  • 创建一个列出非法子项的属性,例如 [ContentTypesDisallowed("BadWidget")]
  • 将属性或方法添加到您的项目类中以表示此数据,例如 [TargetMustRespondTo( EditCommands.Cut)] public class CutTool { ... }
  • 将数据添加到项目类中以表示此数据,例如 new ToolBoxItem { Name="Widget", AllowedContainers=new[] { typeof( MyObject), typeof(OtherObject) }
  • 以上的组合

所有这些都是可行的技术。考虑您自己的情况,看看哪一个最适合实施。

要实际实现隐藏工具箱项,只需使用绑定到工具箱项的 DataTemplate 中的 Visibilty 属性的 IMultiValueConverter 即可。将两个绑定传递给转换器:您的工具箱项目和您的目标。然后在 IMultiValueConverter 中实现您决定的逻辑。

例如,在最简单的情况下,您只关心 ContentAttribute 的集合类型,此代码将起作用:

public object Convert(object[] values, ...)
{
  var toolItem = values[0] as ToolItem;
  var container = values[1];

  var contentPropertyAttribute =
    container.GetType().GetCustomAttributes()
    .OfType<ContentPropertyAttribute>()
    .FirstOrDefault();
  if(contentPropertyAttribute!=null)
  {
    var contentProperty = container.GetType().GetProperty(contentPropertyAttribute.Name);
    if(contentProperty!=null &&
       contentProperty.Type.IsGeneric &&
       contentProperty.Type.GetGenericArguments()[0].IsAssignableFrom(toolItem.Type))
      return Visibility.Visible;
  }
  return Visibility.Collapsed;
}

在实际情况下,事情可能会更复杂一些,例如并非所有 Content 属性都是 ICollection,因此您必须进行额外的检查并可能实现更多的算法。添加一些缓存也是一个好主意,这样您就不会频繁使用反射。

I think the best way is the one taken by Visual Studio itself: Each object describes what it can contain by the types of its properties.

So for Visual Studio I can create a new object that can hold Widgets like this:

[ContentAttribute("Children")]
public class MyObject
{
  ...
  public ICollection<Widget> Children { get ... set ... }
}

the Visual Studio type system does the rest.

Of course there are limitations here that might necessitate some extensions or even another technique. For example, if your object can accept two different types of content that don't share a common base class except for object, you will have to declare your property as ICollection<object> in which case your toolbox won't know what it can really take unless you have an additional mechanism.

Many add-on mechanisms could be used for these special cases, for example:

  • Allow multiple "content" attributes, for example ICollection<Widget> WidgetChildren { get ... set ...}; ICollection<Doodad> DoodadChildren { get ... set ... }
  • Create an attribute you can apply to the class to give it an object type it can contain, for example [ContentTypeAllowed(typeof(Widget))] [ContentTypeAllowed(typeof(Doodad))] where your actual content property is IEnumerable<object>
  • Create an attribute that just has a list of class names, for example [ContentTypesAllowed("Widget,Doodad")]
  • Create a method that, if defined in the target object, evaluates a potential content class and returns true if it can be a child or false if not, something like this bool CanAcceptChildType(Type childType) { return type.IsSubclassOf(Widget) && !type==typeof(BadWidget); }
  • Create an attribute listing illegal children, for example [ContentTypesDisallowed("BadWidget")]
  • Add attributes or methods to your item class to represent this data, for example [TargetMustRespondTo(EditCommands.Cut)] public class CutTool { ... }
  • Add data to your item class to represent this data, for example new ToolBoxItem { Name="Widget", AllowedContainers=new[] { typeof(MyObject), typeof(OtherObject) }
  • Combinations of the above

All of these are viable techniques. Consider your own situation to see which one(s) make the most sense to implement.

To actually implement hiding your tool box item, just use a IMultiValueConverter bound to a Visibilty property in your tool box item's DataTemplate. Pass two bindings to the converter: your tool box item and your target. Then implement the logic you have decided on in your IMultiValueConverter.

For example, in the simplest case where you only care about the collection type of the ContentAttribute, this code would work:

public object Convert(object[] values, ...)
{
  var toolItem = values[0] as ToolItem;
  var container = values[1];

  var contentPropertyAttribute =
    container.GetType().GetCustomAttributes()
    .OfType<ContentPropertyAttribute>()
    .FirstOrDefault();
  if(contentPropertyAttribute!=null)
  {
    var contentProperty = container.GetType().GetProperty(contentPropertyAttribute.Name);
    if(contentProperty!=null &&
       contentProperty.Type.IsGeneric &&
       contentProperty.Type.GetGenericArguments()[0].IsAssignableFrom(toolItem.Type))
      return Visibility.Visible;
  }
  return Visibility.Collapsed;
}

In real situations things may be a little more complex, for example not all Content properties are ICollection, so you'll have to do additional checking and maybe implement more algorithms. It would also be a good idea to add some caching so you aren't using reflection as frequently.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文