【问题标题】:Targeting nested content in Xaml displayed using ContentPresenter使用 ContentPresenter 在 Xaml 中定位嵌套内容
【发布时间】:2013-07-19 08:50:55
【问题描述】:

考虑以下用户控件:

这是我编写的自定义用户控件,它有两个嵌套元素。

FilterContent 显示一种特殊类型的标记,用于过滤屏幕右侧的内容

MainContent 托管过滤后的内容。

控件的唯一真正目的是在整个应用程序中提供一致的 UI 和动画,因为这种过滤器/内容模式经常使用。

用户控件的(简化的)Xaml 如下所示:

<UserControl>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="3*"/>
            <ColumnDefinition Width="7*"/>
        </Grid.ColumnDefinitions>
        <ContentPresenter Grid.Column="0"  Content="{Binding ElementName=filterControl, Path=FilterControl}" DataContext="{Binding}" />
        <ContentPresenter Grid.Column="1"  Content="{Binding ElementName=filterControl, Path=MainControl}" DataContext="{Binding}" />
    </Grid>

后面的代码是:

public sealed partial class FilterPaneControl : UserControl
{
    public static DependencyProperty FilterControlProperty = DependencyProperty.Register("FilterControl", typeof(object), typeof(FilterPaneControl), new PropertyMetadata(default(object), PropertyChangedCallback));

    public static DependencyProperty MainControlProperty = DependencyProperty.Register("MainControl", typeof (object), typeof (FilterPaneControl), new PropertyMetadata(default(object)));

    public FilterPaneControl()
    {
        this.InitializeComponent();
    }

    public object FilterControl
    {
        get { return (object)GetValue(FilterControlProperty); }
        set { SetValue(FilterControlProperty, value); }
    }

    public object MainControl
    {
        get { return (object) GetValue(MainControlProperty); }
        set { SetValue(MainControlProperty, value); }
    }
}

控件在实现页面中的用法是:

    <Generic:FilterPaneControl>
        <Generic:FilterPaneControl.FilterControl>
            <Grid>
                <TextBlock Text="Filter Content here"/>
            </Grid>
        </Generic:FilterPaneControl.FilterControl>
        <Generic:FilterPaneControl.MainControl>
            <Grid>
                <TextBlock Text="Main Content here"/>
            </Grid>
        </Generic:FilterPaneControl.MainControl>
    </Generic:FilterPaneControl>

效果很好!

问题

问题是当我想从实现页面引用控件中的一些内容时。一个很好的例子是处理快照/纵向的视觉状态(WinRT 实现)

    <Generic:FilterPaneControl>
        <Generic:FilterPaneControl.FilterControl>
            <Grid>
                <TextBlock x:Name="filterContent1"  Text="Filter Content here"/>
            </Grid>
        </Generic:FilterPaneControl.FilterControl>
        <Generic:FilterPaneControl.MainControl>
            <Grid>
                <TextBlock Text="Main Content here"/>
            </Grid>
        </Generic:FilterPaneControl.MainControl>
    </Generic:FilterPaneControl>

    <VisualStateManager.VisualStateGroups>
        <VisualState x:Name="FullScreenPortrait">
            <Storyboard>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="filterContent1" Storyboard.TargetProperty="Width">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="200"/>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>
    </VisualStateManager.VisualStateGroups>

这会导致运行时异常,因为即使它存在于可视树中,visualstatemanager 也无法找到引用的元素“filterContent1”。 此外,如果我尝试直接在 Page.Loaded 事件处理程序中引用该元素,则 filterContent1 为空。

好像嵌套的 Xaml 直到稍后才会呈现 - 这也抛出了 visualstatemanager。

有什么建议吗?

【问题讨论】:

    标签: wpf xaml winrt-xaml


    【解决方案1】:

    首先,VisualStateManager 应该与它所针对的元素放在一个面板中,否则它将是一个异常。对于您的情况,结果如下:

    <Generic:FilterPaneControl>
        <Generic:FilterPaneControl.FilterControl>
            <Grid>
                <VisualStateManager.VisualStateGroups>
                    <VisualState x:Name="FullScreenPortrait">
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="filterContent1" Storyboard.TargetProperty="Width">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="200"/>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                </VisualStateManager.VisualStateGroups>
    
                <TextBlock x:Name="filterContent1" Text="Filter Content here"/>
            </Grid>
        </Generic:FilterPaneControl.FilterControl>
    
        ...
    

    其次,通常将VisualStateManager 放在Template / StyleUserControl 中。状态转换可以在代码中或通过 XAML(使用特殊技术)执行。代码后面的设置状态示例:

    VisualStateManager.GoToState(NameOfControl, "State1", true);
    

    第三,以某种方式:

    <Storyboard>
         <ObjectAnimationUsingKeyFrames Storyboard.TargetName="filterContent1" Storyboard.TargetProperty="Width">
               <DiscreteObjectKeyFrame KeyTime="0" Value="200"/>
         </ObjectAnimationUsingKeyFrames>
    </Storyboard>
    

    Width 未设置,在我的情况下是一个例外。我们需要使用类似这样的动画:

    <Storyboard Storyboard.TargetName="filterContent1" Storyboard.TargetProperty="Width">
        <DoubleAnimation To="200" Duration="0:0:1.0"/>
    </Storyboard>
    

    为了证明他的话,我举个例子:

    MainWindow

    <Window x:Class="VSMinUserControlHelp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        xmlns:local="clr-namespace:VSMinUserControlHelp"
        Title="MainWindow" Height="350" Width="525"
        WindowStartupLocation="CenterScreen">
    
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition Height="40"/>
            </Grid.RowDefinitions>
    
            <local:UserControl1 x:Name="Control1" Height="118" VerticalAlignment="Top" Margin="50,12,101,0" />
    
            <StackPanel Orientation="Horizontal" Grid.Row="1">
                <Button Name="State1Button" Width="75" Click="State1Button_Click">State1</Button>
            </StackPanel>
        </Grid>
    </Window>
    

    Code behind

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();        
        }
    
        private void State1Button_Click(object sender, RoutedEventArgs e)
        {
            VisualStateManager.GoToState(Control1, "State1", true);
        }
    }
    

    UserControl

    <UserControl x:Class="VSMinUserControlHelp.UserControl1"
             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" 
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             mc:Ignorable="d"              
             d:DesignHeight="300" d:DesignWidth="300">
    
        <Grid>
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="Common1">
                    <VisualState x:Name="State1">
                        <Storyboard Storyboard.TargetName="filterContent1" Storyboard.TargetProperty="Width">
                            <DoubleAnimation To="200" Duration="0:0:1.0"/>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
    
            <TextBlock x:Name="filterContent1" Background="Aqua" Width="100" HorizontalAlignment="Left" Text="Filter Content here"/>
        </Grid>
    </UserControl>
    

    Note: 示例在 VS 2010、Windows XP 上运行,未在 WinRT 下测试

    【讨论】:

    • 感谢您的回复。我之前考虑过您的解决方案,但不愿实施它,因为尽管它可能解决 VisualStateManager 问题,但当我希望从父容器引用子内容中的元素时,它仍然无法解决其他场景。 (在 Page.Load 中引用子元素为空!)我今天会试一试,至少尝试解决 VSM 问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多