用户控件中的 WPF 将控件模板的内容设置为依赖属性的值

发布于 2024-10-25 20:44:09 字数 3964 浏览 1 评论 0原文

我对 WPF 还很陌生(现在使用它已经 3 周了),所以我可能会错过一些愚蠢的东西或者不明白我在做什么!

我正在尝试创建一个模态类型弹出窗口,该窗口将覆盖整个应用程序或它所在的当前控件。我希望此控件是半透明的,以便用户仍然可以看到后面的内容,但无法使用它。然后,他们将能够在继续之前完成模式窗口中的内容。

我不想在不同的地方重复代码,因此我的目标是拥有一个可以在 XAML 中使用的通用控件,并且每次只需添加我需要的内容。即,褪色、透明度、背景颜色额外都在一个地方处理,我只需要为其该实例添加特定功能。

到目前为止,我已经创建了一个名为 jonblind 的用户控件:

<UserControl x:Class="ShapInteractiveClient.View.SampleTests.jonblind"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">


<Grid x:Name="blindGrid" Grid.RowSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
      Opacity="0.82">
    <Grid.Background>
        <LinearGradientBrush EndPoint="0,1" StartPoint="0,0" MappingMode="RelativeToBoundingBox">
            <GradientStop Color="#FFA8CBFE" Offset="1"/>
            <GradientStop Color="Red"/>
            <GradientStop Color="#FFE1EDFE" Offset="0.147"/>
        </LinearGradientBrush>
    </Grid.Background>

    <ContentControl x:Name="contentTemplateArea" />

</Grid>

</UserControl>

该控件背后的代码如下:

public partial class jonblind : UserControl
{
    public jonblind()
    {
        InitializeComponent();
        SetVisibility(this);
    }

    [Category("jonblind")]
    public bool IsContentVisible
    {
        get { return (bool)GetValue(IsContentVisibleProperty); }
        set { SetValue(IsContentVisibleProperty, value); }
    }

    public static readonly DependencyProperty IsContentVisibleProperty = DependencyProperty.Register("IsContentVisible", typeof(bool), typeof(jonblind),
        new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsOverlayContentVisibleChanged)));

    private static void OnIsOverlayContentVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        jonblind blind = d as jonblind;
        if(blind != null)
            SetVisibility(blind);
    }

    private static void SetVisibility(jonblind blind)
    {
        blind.blindGrid.Visibility = blind.IsContentVisible ? Visibility.Visible : Visibility.Hidden;
    }



    [Category("jonblind")]
    public ContentControl ContentAreaControl
    {
        get { return (ContentControl)GetValue(ContentAreaControlProperty); }
        set { SetValue(ContentAreaControlProperty, value); }
    }

    public static readonly DependencyProperty ContentAreaControlProperty = DependencyProperty.Register("ContentAreaControl", typeof(ContentControl), typeof(jonblind),
        new FrameworkPropertyMetadata(new PropertyChangedCallback(OnContentAreaControlChanged)));

    private static void OnContentAreaControlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        jonblind blind = d as jonblind;
        if (blind != null && e.NewValue != null && e.NewValue is ContentControl)
        {
            blind.contentTemplateArea = e.NewValue as ContentControl;
        }
    }
}

我可以将其添加到另一个用户控件,如下所示:

<UserControl.Resources>
<ContentControl x:Key="testcontrol">
        <StackPanel>
            <TextBox VerticalAlignment="Center" HorizontalAlignment="Center" Text="Loading!!!" />
            <Button Content="hide me!" Command="{Binding Path=alternateblind}" />
        </StackPanel>
    </ContentControl>
</UserControl.Resources>

<SampleTests:jonblind IsContentVisible="{Binding Path=ShowBlind}" ContentAreaControl="{StaticResource testcontrol}" />

如果我在“OnContentAreaControlChanged”上放置一个断点,我可以看到传入的新内容但它永远不会在运行时显示它。

我不知道我是否做错了,是否有可能,或者是否只需要调整。对此以及处理这种情况的任何和所有建议将不胜感激。

I am pretty new to WPF, (using it for 3 weeks now), so I might be missing something stupid or not understanding what I am doing!

I am trying to create a modal type popup window that would cover either the whole application or current control it is in. I want this control to be semi transparent so the user can still see the content behind but not be able to use it. They will then be able to complete what ever is in the modal window before continuing.

I do not want to have to repeat code in different places so my goal is to have a generic control that I can use in my XAML and only have to add the content I need each time. I.e. the fading, transparency, background colour extra are all handled in one place and I only have to add the specific functionality for that instance of it.

So far I have created a usercontrol called jonblind:

<UserControl x:Class="ShapInteractiveClient.View.SampleTests.jonblind"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">


<Grid x:Name="blindGrid" Grid.RowSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
      Opacity="0.82">
    <Grid.Background>
        <LinearGradientBrush EndPoint="0,1" StartPoint="0,0" MappingMode="RelativeToBoundingBox">
            <GradientStop Color="#FFA8CBFE" Offset="1"/>
            <GradientStop Color="Red"/>
            <GradientStop Color="#FFE1EDFE" Offset="0.147"/>
        </LinearGradientBrush>
    </Grid.Background>

    <ContentControl x:Name="contentTemplateArea" />

</Grid>

</UserControl>

I have a code behind for the control as follows:

public partial class jonblind : UserControl
{
    public jonblind()
    {
        InitializeComponent();
        SetVisibility(this);
    }

    [Category("jonblind")]
    public bool IsContentVisible
    {
        get { return (bool)GetValue(IsContentVisibleProperty); }
        set { SetValue(IsContentVisibleProperty, value); }
    }

    public static readonly DependencyProperty IsContentVisibleProperty = DependencyProperty.Register("IsContentVisible", typeof(bool), typeof(jonblind),
        new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsOverlayContentVisibleChanged)));

    private static void OnIsOverlayContentVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        jonblind blind = d as jonblind;
        if(blind != null)
            SetVisibility(blind);
    }

    private static void SetVisibility(jonblind blind)
    {
        blind.blindGrid.Visibility = blind.IsContentVisible ? Visibility.Visible : Visibility.Hidden;
    }



    [Category("jonblind")]
    public ContentControl ContentAreaControl
    {
        get { return (ContentControl)GetValue(ContentAreaControlProperty); }
        set { SetValue(ContentAreaControlProperty, value); }
    }

    public static readonly DependencyProperty ContentAreaControlProperty = DependencyProperty.Register("ContentAreaControl", typeof(ContentControl), typeof(jonblind),
        new FrameworkPropertyMetadata(new PropertyChangedCallback(OnContentAreaControlChanged)));

    private static void OnContentAreaControlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        jonblind blind = d as jonblind;
        if (blind != null && e.NewValue != null && e.NewValue is ContentControl)
        {
            blind.contentTemplateArea = e.NewValue as ContentControl;
        }
    }
}

I can add it to another usercontrol as follows:

<UserControl.Resources>
<ContentControl x:Key="testcontrol">
        <StackPanel>
            <TextBox VerticalAlignment="Center" HorizontalAlignment="Center" Text="Loading!!!" />
            <Button Content="hide me!" Command="{Binding Path=alternateblind}" />
        </StackPanel>
    </ContentControl>
</UserControl.Resources>

<SampleTests:jonblind IsContentVisible="{Binding Path=ShowBlind}" ContentAreaControl="{StaticResource testcontrol}" />

If I put a break point on "OnContentAreaControlChanged" I can see the new content being passed in but it will never display it at runtime.

I don't know if I am going about this all wrong, if it is even possible or if it just needs tweeking. Any and all advice on this and dealing with this kind of scenario would be greatly appreciated.

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

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

发布评论

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

评论(3

桃扇骨 2024-11-01 20:44:09

虽然这不是您问题的直接答案,但您应该将内容放在控件内,而不是使用依赖属性,这样更具可读性。不要使用 UserControl,而是创建一个扩展 ContentControl 的类:

public class jonblind : ContentControl
{
    [Category("jonblind")]
    public bool IsContentVisible
    {
        get { return (bool)GetValue(IsContentVisibleProperty); }
        set { SetValue(IsContentVisibleProperty, value); }
    }

    public static readonly DependencyProperty IsContentVisibleProperty = DependencyProperty.Register("IsContentVisible", typeof(bool), typeof(jonblind),
        new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsOverlayContentVisibleChanged)));

    private static void OnIsOverlayContentVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        jonblind blind = d as jonblind;
        if(blind != null)
            SetVisibility(blind);
    }

    private static void SetVisibility(jonblind blind)
    {
        blind.blindGrid.Visibility = blind.IsContentVisible ? Visibility.Visible : Visibility.Hidden;
    }
}

然后使用样式来指定内容

<Style TargetType="control:jonblind">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="control:jonblind">
                <Grid>
                    <Grid.Background>
                        <LinearGradientBrush EndPoint="0,1" StartPoint="0,0" MappingMode="RelativeToBoundingBox">
                            <GradientStop Color="#FFA8CBFE" Offset="1"/>
                            <GradientStop Color="Red"/>
                            <GradientStop Color="#FFE1EDFE" Offset="0.147"/>
                        </LinearGradientBrush>
                     </Grid.Background>
                    <ContentControl Content="{TemplateBinding Content}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

,最后 - 使用它

<control:jonblind IsContentVisible="{Binding Path=ShowBlind}">            
    <StackPanel>
        <TextBox VerticalAlignment="Center" HorizontalAlignment="Center" Text="Loading!!!" />
        <Button Content="hide me!" Command="{Binding Path=alternateblind}" />
    </StackPanel>
</control:jonblind>

(改编自此线程的示例:如何创建具有命名内容的 WPF UserControl)

While this is not a direct answer to your problem, but you should put the content inside the control instead of using a dependency property, much more readable. Instead of using a UserControl, create a class that extends ContentControl:

public class jonblind : ContentControl
{
    [Category("jonblind")]
    public bool IsContentVisible
    {
        get { return (bool)GetValue(IsContentVisibleProperty); }
        set { SetValue(IsContentVisibleProperty, value); }
    }

    public static readonly DependencyProperty IsContentVisibleProperty = DependencyProperty.Register("IsContentVisible", typeof(bool), typeof(jonblind),
        new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsOverlayContentVisibleChanged)));

    private static void OnIsOverlayContentVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        jonblind blind = d as jonblind;
        if(blind != null)
            SetVisibility(blind);
    }

    private static void SetVisibility(jonblind blind)
    {
        blind.blindGrid.Visibility = blind.IsContentVisible ? Visibility.Visible : Visibility.Hidden;
    }
}

then use a style to specify the contents

<Style TargetType="control:jonblind">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="control:jonblind">
                <Grid>
                    <Grid.Background>
                        <LinearGradientBrush EndPoint="0,1" StartPoint="0,0" MappingMode="RelativeToBoundingBox">
                            <GradientStop Color="#FFA8CBFE" Offset="1"/>
                            <GradientStop Color="Red"/>
                            <GradientStop Color="#FFE1EDFE" Offset="0.147"/>
                        </LinearGradientBrush>
                     </Grid.Background>
                    <ContentControl Content="{TemplateBinding Content}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

and finally - use it

<control:jonblind IsContentVisible="{Binding Path=ShowBlind}">            
    <StackPanel>
        <TextBox VerticalAlignment="Center" HorizontalAlignment="Center" Text="Loading!!!" />
        <Button Content="hide me!" Command="{Binding Path=alternateblind}" />
    </StackPanel>
</control:jonblind>

(Example adapted from this thread: How to create a WPF UserControl with NAMED content)

情丝乱 2024-11-01 20:44:09

这是 WPF 中的常见模式,许多派生自 ContentControl(1 个子内容)、HeaderedContentControl(2 个子内容)或 ItemsControl(n 个子内容的集合)的内置控件都使用该模式。一般来说,内容属性(将在运行时替换为占位符的内容)应该是对象类型。在这种情况下,您还可以摆脱更改处理程序并仅依赖数据绑定。

[Category("jonblind")]
public object ContentAreaControl
{
    get { return GetValue(ContentAreaControlProperty); }
    set { SetValue(ContentAreaControlProperty, value); }
}

public static readonly DependencyProperty ContentAreaControlProperty = 
    DependencyProperty.Register("ContentAreaControl", typeof(object), typeof(jonblind),
    new FrameworkPropertyMetadata(null));

有了这个新的 Content 属性,您就可以使用 ContentPresenter 设置 Binding,它只是充当传入内容的占位符。在从 ContentControl 派生的自定义控件中设置这一点甚至更容易,其中 ContentPresenter 可以连接到内容自动包含在 ControlTemplate 中。

<UserControl x:Class="ShapInteractiveClient.View.SampleTests.jonblind"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">

<Grid x:Name="blindGrid" Grid.RowSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
      Opacity="0.82">
    <Grid.Background>
        <LinearGradientBrush EndPoint="0,1" StartPoint="0,0" MappingMode="RelativeToBoundingBox">
            <GradientStop Color="#FFA8CBFE" Offset="1"/>
            <GradientStop Color="Red"/>
            <GradientStop Color="#FFE1EDFE" Offset="0.147"/>
        </LinearGradientBrush>
    </Grid.Background>

    <ContentPresenter Content="{Binding Path=ContentAreaContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />

</Grid>
</UserControl>

一般来说,将 UIElement 实例声明为资源(更喜欢将它们放在模板资源中)是一个坏主意,但我们可以通过像任何其他控件一样对待它来轻松解决这个问题。其用法更像是 ContentControl(如 Button):

<SampleTests:jonblind IsContentVisible="{Binding Path=ShowBlind}">
    <SampleTests:jonblind.ContentAreaControl>
        <StackPanel>
            <TextBox VerticalAlignment="Center" HorizontalAlignment="Center" Text="Loading!!!" />
            <Button Content="hide me!" Command="{Binding Path=alternateblind}" />
        </StackPanel>
    </SampleTests:jonblind.ContentAreaControl>
</SampleTests:jonblind>

您可以通过将其作为自定义 ContentControl 而不是 UserControl 来获得更多优势,但会增加一些复杂性,并且通常需要更好地理解这些概念有助于使其正常工作。当您开始使用 UserControl 时,可以更轻松地完成您需要完成的任务。

This is a common pattern in WPF that is used by a lot of the built in controls deriving from ContentControl (1 piece of child content), HeaderedContentControl (2 pieces of child content), or ItemsControl (a collection of n children). In general, the Content properties (the stuff that will be substituted into placeholders at runtime) should be of type object. You can also get rid of the change handler and just rely on data binding in this situation.

[Category("jonblind")]
public object ContentAreaControl
{
    get { return GetValue(ContentAreaControlProperty); }
    set { SetValue(ContentAreaControlProperty, value); }
}

public static readonly DependencyProperty ContentAreaControlProperty = 
    DependencyProperty.Register("ContentAreaControl", typeof(object), typeof(jonblind),
    new FrameworkPropertyMetadata(null));

With this new Content property you can then set up a Binding using a ContentPresenter, which simply acts as a placeholder for your Content that's passed in. This is even easier to set up in custom controls derived from ContentControl where the ContentPresenter can be wired up to the Content automatically inside a ControlTemplate.

<UserControl x:Class="ShapInteractiveClient.View.SampleTests.jonblind"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">

<Grid x:Name="blindGrid" Grid.RowSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
      Opacity="0.82">
    <Grid.Background>
        <LinearGradientBrush EndPoint="0,1" StartPoint="0,0" MappingMode="RelativeToBoundingBox">
            <GradientStop Color="#FFA8CBFE" Offset="1"/>
            <GradientStop Color="Red"/>
            <GradientStop Color="#FFE1EDFE" Offset="0.147"/>
        </LinearGradientBrush>
    </Grid.Background>

    <ContentPresenter Content="{Binding Path=ContentAreaContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />

</Grid>
</UserControl>

In general it's a bad idea to declare UIElement instances as Resources (prefer placing them inside Template Resources instead) but we can get around that issue easily by treating this like any other control. The usage is then much more like what a ContentControl (like Button) looks like:

<SampleTests:jonblind IsContentVisible="{Binding Path=ShowBlind}">
    <SampleTests:jonblind.ContentAreaControl>
        <StackPanel>
            <TextBox VerticalAlignment="Center" HorizontalAlignment="Center" Text="Loading!!!" />
            <Button Content="hide me!" Command="{Binding Path=alternateblind}" />
        </StackPanel>
    </SampleTests:jonblind.ContentAreaControl>
</SampleTests:jonblind>

You can get even more advantages from doing this as a custom ContentControl instead of a UserControl, but there is some added complexity and a better understanding of the concepts is generally helpful to make it work correctly. While you're starting out sticking with a UserControl can make it simpler to get what you need done.

Smile简单爱 2024-11-01 20:44:09

对于遇到与此类似问题但在其他地方找不到答案的任何人:

如果您想将子控件的属性绑定到用户控件中的依赖属性,然后将属性绑定到 UI 中的依赖属性,如下所示:

<Page>
    <my:usercontrol MyCustomPoperty="{Binding MyData}"/>
</Page>

您必须执行以下操作(花了我几个小时才弄清楚):

<UserControl x:Class="my.usercontrol">
    <TextBox Text="{Binding MyCustomProperty}">
</UserControl>

在构造函数后面的代码中:

public usercontrol()
{
    InitializeComponent();
    (this.Content as FrameworkElement).DataContext = this;
}

这将组合控件的 DataContext 设置为用户控件,以便这些控件可以绑定到您的自定义依赖属性,同时保留DataContext 由您的 UI 设置(本例中的页面)。

现在,您的 UI 更新用户控件的 MyCustomProperty,这反过来又更新与其绑定的用户控件中的 TextBox。

资料来源: http://blog.jerrynixon.com /2013/07/solved-two-way-binding-inside-user.html

For anyone encountering a similar issue to this one but couldn't find an answer somewhere else:

If you want to bind a child control's property to a dependency property in the user control and later bind a property to that dependency property in your UI like this:

<Page>
    <my:usercontrol MyCustomPoperty="{Binding MyData}"/>
</Page>

You have to do the following (Took me hours to figure out):

<UserControl x:Class="my.usercontrol">
    <TextBox Text="{Binding MyCustomProperty}">
</UserControl>

In code behind constructor:

public usercontrol()
{
    InitializeComponent();
    (this.Content as FrameworkElement).DataContext = this;
}

This sets the DataContext for your composed controls to the usercontrol, so these controls can bind to your custom Dependency Property, while at the same preserve the DataContext set by your UI (The page in this example).

Now your UI Updates the usercontrols's MyCustomProperty which in return updates the TextBox in the usercontrol that binds to it.

Source: http://blog.jerrynixon.com/2013/07/solved-two-way-binding-inside-user.html

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