【问题标题】:Visual Studio Designer shows empty Window when using custom ContentPropertyAttribute使用自定义 ContentPropertyAttribute 时,Visual Studio 设计器显示空窗口
【发布时间】:2015-09-20 01:45:08
【问题描述】:

我的应用程序有很多窗口,其中大多数共享一些基本功能。因此,我扩展了 Window 类来为我的所有窗口创建一个基础。

一切编译和显示都很好,但是当我使用我的窗口类时,设计器只显示一个空窗口。

我做了一个可以轻松使用的基本示例,我的真实窗口要复杂得多,但这说明了问题。 代码如下:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Markup;

namespace WpfApplication1
{
    [ContentProperty("ContentElement")]
    public class MyWindow : Window
    {
        public ToolBar ToolBar { get; private set; }
        public StatusBar StatusBar { get; private set; }
        public Border ContentBorder { get; private set; }

        public UIElement ContentElement
        {
            get { return (UIElement)GetValue(ContentElementProperty); }
            set { SetValue(ContentElementProperty, value); }
        }
        public static readonly DependencyProperty ContentElementProperty = DependencyProperty.Register(
            "ContentElement", typeof(UIElement), typeof(MyWindow),
            new PropertyMetadata(null, (d, e) =>
             {
                 MyWindow w = (MyWindow)d;
                 w.ContentBorder.Child = (UIElement)e.NewValue;
             }));

        public MyWindow() : base()
        {
            ToolBar = new ToolBar();
            ToolBar.Height = 30;
            ToolBar.VerticalAlignment = VerticalAlignment.Top;

            StatusBar = new StatusBar();
            StatusBar.Height = 20;
            StatusBar.VerticalAlignment = VerticalAlignment.Bottom;

            ContentBorder = new Border();
            ContentBorder.SetValue(MarginProperty, new Thickness(0, 30, 0, 20));

            Grid grid = new Grid();
            grid.Children.Add(ToolBar);
            grid.Children.Add(ContentBorder);
            grid.Children.Add(StatusBar);
            Content = grid;
        }
    }
}

使用 MyWindow 的 XAML 示例:

<local:MyWindow x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="300">
    <Grid>
        <Rectangle Fill="Blue" />
    </Grid>
</local:MyWindow>

UserControl 做同样的事情就很好,在设计器中也是如此。如果您想尝试,只需将每次出现的MyWindow 替换为MyUserControl 并从UserControl 扩展即可。

有什么方法可以让我像这样自定义Window 与设计师一起工作,还是我必须制作一个UserControl 并在每个窗口中使用它? 另外,这是某种错误还是预期行为?

附加信息:我正在运行 Visual Studio 2015 社区并且我正在使用 .net 4.6

我还尝试了另一种方法。我没有使用 ContentPropertyAttribute,而是像这样覆盖了 ContentProperty:

new public object Content {
    get { return GetValue(ContentProperty); }
    set { SetValue(ContentProperty, value); }
}
new public static DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(BaseUserControl), new PropertyMetadata(null, (s, e) =>
{
    MyWindow bw = (MyWindow)s;
    bw.ContentBorder.Child = (UIElement)e.NewValue;
}));

这同样适用于UserControl。有了Window,我现在至少可以在设计器中看到内容,但是ToolBarStatusBar 仍然没有出现在设计器中。运行时一切正常。

【问题讨论】:

    标签: c# wpf visual-studio designer


    【解决方案1】:

    首先,我不是 WPF 方面的超级专家,但我做了很多工作,我认为我可以提供并帮助澄清一些组件。首先,您不能从基于 .XAML 的 WPF-Window 声明派生,它只能在完全在代码中的情况下。我发现有时在 XAML 中构建可视元素比在代码中更容易,但两者都可以并且确实有效。

    也就是说,我想提供一个可能对您有用的解决方案。从WPF Window Style / Templatea 开始,如果您还不熟悉它们,以及其他控件,您可以运行它们的默认值。

    首先,我从 RESOURCE DICTIONARY STYLE 定义开始,它将模仿您在默认形式中可能想要的大部分内容。这成为样式定义的“ControlTemplate”中的内容。我已在我的机器上创建的根级别 WpfApplication1 将其创建为文件“MyWindowStyle.xaml”(只是为了匹配您的示例项目文件命名空间引用)。

    在模板中,您几乎可以拥有任何东西...网格、停靠面板、堆栈面板等。在这种情况下,我使用了 DockPanel 并添加了您的示例工具栏、状态栏和两个额外的标签,仅作为示例。我还预设了尺寸和假颜色,以便在您确认零件的影响时提供零件的可视化。

    要查看的关键元素是 .这确定了您的每个 DERIVED Windows 内容的内容将被放置在哪里......将其视为您的每个表单的占位符以保持个性,而表单的其余部分,其控件都保持一致。当您玩弄它时,您会看到它开始发挥作用。

    它的内容是并注意样式 x:Key="MyWindowStyle"。巧合的是,这与 xaml 相同,但您可以在单个资源字典中拥有 100 种样式。出于演示目的,我将保持简单。

    <ResourceDictionary 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    
        <Style x:Key="MyWindowStyle" TargetType="Window">
            <Setter Property="SnapsToDevicePixels" Value="true" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Window}">
                        <Grid>
                            <Grid.Background>
                                <SolidColorBrush Color="{DynamicResource WindowColor}"/>
                            </Grid.Background>
                            <AdornerDecorator>
                                <DockPanel LastChildFill="True" Background="Blue">
                                    <!-- List items docked to the top based on top-most first going down -->
                                    <ToolBar x:Name="tmpToolBar" Height="45" DockPanel.Dock="Top" />
                                    <Label Content="Testing by Style"
                                           Height="30" Width="150" DockPanel.Dock="Top"/>
    
                                    <!-- When docking to the bottom, start with bottom most working up -->
                                    <StatusBar x:Name="tmpStatusBar" Height="30" 
                                        Background="Yellow" DockPanel.Dock="Bottom" />
                                    <Label Content="Footer area based from style"
                                           Height="30" Width="250" DockPanel.Dock="Bottom" />
    
                                    <!-- This one, since docked last is rest of the space of the window -->
                                    <ContentPresenter DockPanel.Dock="Bottom"/>
                                </DockPanel>
                            </AdornerDecorator>
                            <ResizeGrip x:Name="WindowResizeGrip"
                                HorizontalAlignment="Right"
                                VerticalAlignment="Bottom"
                                Visibility="Collapsed"
                                IsTabStop="false" />
                        </Grid>
    
                        <ControlTemplate.Triggers>
                            <Trigger Property="ResizeMode" Value="CanResizeWithGrip">
                                <Setter TargetName="WindowResizeGrip" 
                                        Property="Visibility" Value="Visible" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>   
    

    接下来,我们需要在应用程序的整个过程中公开它,包括在设计器模式下的可用性...在您的项目“App.xaml”中,它是应用程序的启动,它将有一个默认值和空白区域。用这个替换它。

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary  Source="pack://application:,,,/WpfApplication1;component/MyWindowStyle.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
    

    现在,转到“MyWindow.cs”类的纯代码(不是基于 .xaml 窗口的定义)。如果您查看我声明工具栏和状态栏的样式,我分别为它们分配了“tmpToolBar”和“tmpStatusBar”的名称。注意 [TemplatePart()] 声明。我现在期望模板在某处的 TEMPLATE 中按给定名称拥有这些控件。

    在构造函数中,我从完全可用的 App.xaml 资源字典中加载样式。然后我跟进 OnApplyTemplate(),我通常会大量记录我的代码,因此任何关注我的人都知道事情的来源和来源以及自我解释。

    我的整个“MyClass.cs”在下面

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Media;
    
    namespace WpfApplication1
    {
        [TemplatePart(Name = "tmpToolBar", Type = typeof(ToolBar))]
        [TemplatePart(Name = "tmpStatusBar", Type = typeof(StatusBar))]
        public class MyWindow : Window
        {
            protected ToolBar myToolBar;
            protected StatusBar myStatusBar;
    
            public MyWindow() : base()
            {
                // NOW, look for the resource of "MyWindowStyle" within the dictionary
                var tryStyle = FindResource("MyWindowStyle") as Style;
                // if a valid find and it IS of type Style, set the style of 
                // the form to this pre-defined format and all it's content
                if (tryStyle is Style)
                    Style = tryStyle;
            }
    
            // the actual template is not applied until some time after initialization.
            // at that point, we can then look to grab object references to the controls
            // you have need to "hook up" to.
            public override void OnApplyTemplate()
            {
                // first allow default to happen
                base.OnApplyTemplate();
    
                // while we get the style loaded, we can now look at the expected template "parts"
                // as declared at the top of this class.  Specifically looking for the TEMPLATE
                // declaration by the name "tmpToolBar" and "tmpStatusBar" respectively.
    
                // get object pointer to the template as defined in the style template
                // Now, store those object references into YOUR Window object reference of Toolbar
                var myToolBar = Template.FindName("tmpToolBar", this) as ToolBar;
                if (myToolBar != null)
                    // if you wanted to add your own hooks to the toolbar control
                    // that is declared in the template
                    myToolBar.PreviewMouseDoubleClick += myToolBar_PreviewMouseDoubleClick;
    
                // get object pointer to the template as defined in the style template
                var myStatusBar = Template.FindName("tmpStatusBar", this) as StatusBar;
                if (myStatusBar != null)
                    myStatusBar.MouseDoubleClick += myStatusBar_MouseDoubleClick;
    
                // Now, you can do whatever else you need with these controls downstream to the 
                // rest of your derived window controls
            }
    
            void myToolBar_PreviewMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
            {
                // in case you wanted to do something based on PreviewMouseDoubleClick of the toolbar
                MessageBox.Show("ToolBar: Current Window Class: " + this.ToString());
            }
    
            void myStatusBar_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
            {
                // in case something for MouseDoubleClick on the StatusBar
                MessageBox.Show("StatusBar: Current Window Class: " + this.ToString());
            }
        }
    }
    

    所以现在,让我们把它放在适当的位置。让您的应用程序的主窗口派生自 MyWindow 类。你唯一需要的就是

    namespace WpfApplication1
    {
        public partial class MainWindow : MyWindow
        {}
    }
    

    在表单的 DESIGNER 中,放入一些控件,例如标签、文本框等。你还没有看到你实际的其他风格,但只是随它去吧。保存并运行示例应用程序。您的主窗口应显示整个预定义模板以及您专门放置在此表单上的一些额外控件。

    现在,从设计师的角度在“主窗口”中获得完整的可视化。在

    的 .xaml 区域内
    <my:MyWindow
        x:Class="WpfApplication1.MainWindow"
        [other declarations]  >
    

    只需在关闭之前添加以下内容 ">"

    Style="{StaticResource MyWindowStyle}"
    

    该资源在启动时可通过 App.xaml 获得,您现在应该能够在设计时看到整个视觉效果...但您不能更改最外层的模板,只能更改前面提到的这一页的特定内容关于模板定义的 "ContentPresenter" 部分。您要更改的是模板分配的占位符部分。如果要更改窗口控件的主要部分,则需要更新 TEMPLATE!

    但这是模板设计者的伎俩的一部分。从这个开始,在视觉上构建你需要的东西,把它放在正确的位置,一旦准备好,把它从这里取出并放入模板中,现在它适用于所有窗口的其余部分。修复字体、大小、颜色等。

    希望这会有所帮助,如果您有任何后续问题,请告诉我。

    【讨论】:

    • 谢谢。如果没有更好的结果,我会接受这个作为答案。 Template 的使用是我使用的解决方法之一。遗憾的是,它仍然需要我为每个新窗口设置 Template 属性或 Style 属性。我知道的另一个解决方法是从UserControl 而不是Window 派生,将其添加到xaml 中的窗口,然后通过它访问子控件。我更喜欢Template 解决方案,因为它使访问子控件更容易。
    • 我仍然不明白为什么 Window 在设计器中的行为与 UserControl 不同。当我谈到设计师时,它们实际上是相同的。唯一的区别是Window 有一个灰色边框,据我所知。但好吧,也许它会在未来的一些版本中工作
    • @x4rf41,我的解决方案不需要您在每个新窗口中设置样式/模板...看看 MyWIdow() 构造函数。它查找样式的资源并在运行时为您分配它。你不需要做任何其他事情。然后,当应用模板时,也获取工具栏和状态栏的钩子。因此这些部分可用于您的子派生表单。
    • 我已经尝试过您的解决方案并且它可以工作,但要使其在设计器中工作,我必须在 xaml 中设置模板。还是我做错了什么?让它在设计器中工作是这个练习的全部想法:)。模板解决方法的另一个小弱点是,当我将此窗口放入库中时,我实际上必须将 ResourceDictionary 添加到使用它的每个应用程序(我的嵌套 UserControl 解决方案没有这个问题)。但现在这不是我关心的问题
    • @x4rf41,在应用程序中,您不需要显式添加样式引用,它会自动加载。但是,无论您想要什么特定于窗口的页面,在呈现时都会有所不同。基类 MyWindow 强制加载样式。让我看看除了这个答案之外,把它变成一个库 dll 给你。
    【解决方案2】:

    Window 类与UserControl 类相比非常复杂。微软在Window 类中编写了超过8k 行代码,而UserControl 中编写了80 行代码,另外Window 类在content property 上包含许多操作/事件/限制,并且任何一段代码都会阻碍渲染将[ContentProperty("ContentElement")]Window 子类MyWindow 一起使用时的内容。

    可能将其设为UserControl 是更好的选择,如果不可能,您可以在content property 中临时编写一些代码(从ContentElement 属性复制代码)以查看设计视图。

    <lib:MyWindow.Content>
        <Button  Content="Click"   Width="200"   />
    </lib:MyWindow.Content>
    

    然后在运行前删除代码。 (不是一个好主意,但是,不管怎样。:) 我怀疑你已经想通了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-17
      • 1970-01-01
      • 1970-01-01
      • 2013-07-20
      相关资源
      最近更新 更多