开发者

WPF in a usercontrol set a controltemplate's content to the value of a dependencyproperty

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 开发者_StackOverflow中文版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.


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)


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜