【问题标题】:Creating a Subclass of Page in UWP在 UWP 中创建页面的子类
【发布时间】:2019-07-31 15:18:42
【问题描述】:

我正在尝试做的事情:

创建消费者可以像 UWP 页面一样使用的自定义页面控件,但是,它还会在消费者内容旁边显示它自己的自定义内容。

我尝试了什么:

  • 创建一个新控件,继承自 Page

  • 创建一个从页面继承的模板化控件

  • 创建包含页面的控件

  • 在我的自定义页面中设置ContentProperty 属性并绑定到它

有什么问题?

当我尝试创建一个同时具有xamlxaml.cs 文件的控件时,该文件继承自Page,我在子类控件内的随机控件上得到InvalidCastExceptions

示例:

TestPage.xaml

<Page
    x:Class="ControlSandbox.TestPage"
    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:uwp_toolkit="using:Microsoft.Toolkit.Uwp.UI.Controls"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Page.Content>
        <ContentPresenter Content="{Binding}" />
    </Page.Content>

    <Page.BottomAppBar>
        <AppBar Background="Transparent" x:Name="appbar" IsOpen="True">
            <uwp_toolkit:InAppNotification x:Name="note">
                <StackPanel>
                    <TextBlock>HEADER!</TextBlock>
                    <TextBlock>Message</TextBlock>
                    <StackPanel Orientation="Horizontal">
                        <Button>OK</Button>
                        <Button>Cancel?</Button>
                    </StackPanel>
                </StackPanel>
            </uwp_toolkit:InAppNotification>
        </AppBar>
    </Page.BottomAppBar>
</Page>

TestPage.xaml.cs

public partial class TestPage : Page
{
    public TestPage()
    {
        this.InitializeComponent();
    }

    public void ShowNotification()
    {
        appbar.IsOpen = true;
        note.Show();
    }
}

MainPage.xaml

<controlsandbox:TestPage
    xmlns:controlsandbox="using:ControlSandbox"    x:Class="ControlSandbox.MainPage"
    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"
    mc:Ignorable="d" 
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <Button Click="Button_Click">SHOW NOTIFICATION</Button>
    </Grid>

</controlsandbox:TestPage>

MainPage.xaml.cs

public partial class MainPage : TestPage
{
    public MainPage()
    {
        this.InitializeComponent();
    }

    private void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
    {
        this.ShowNotification();
    }
}

上面的代码导致InvalidCastException,我一辈子都找不到问题。

System.InvalidCastException: '无法转换类型的对象 'Windows.UI.Xaml.Controls.AppBar' 键入 'Windows.UI.Xaml.Controls.Button'。'

现在,如果我执行完全相同的代码,但都在 MainPage.xaml 而不是 TestPage.xaml 中,一切都会按预期工作

更新

所以我相信这是平台中的一个错误。这是我对这个问题所做的演示。请证明我错了,因为这将是一个真正的限制https://github.com/DotNetRussell/UWP_Page_Inheritance_Bug

更新

我为下面的答案添加了更改。似乎当我创建一个普通的模板化控件并将其放在一个普通的 uwp 页面上时,它工作正常。但是,当我创建模板化 Page 时,它会忽略我的模板。

更新

我认为这是平台中的一个错误。我在 github 上打开了一个问题 https://github.com/microsoft/microsoft-ui-xaml/issues/1075

【问题讨论】:

  • 根据您的描述,您想自定义页面吗?
  • @NicoZhu-MSFT 是的,我想要一个自定义页面,它将消费者提供的内容与它自己的默认内容相结合。想象一个控件,默认情况下具有其他元素,如导航窗格、通知栏、上下文栏和其他内容。然后你给它你的内容,它把它放在中间。

标签: c# xaml uwp


【解决方案1】:

创建模板化控件(Project->Add New Item->Visual Studio 中的模板化控件):

public sealed class CustomPage : Page
{
    private AppBar appbar;
    private InAppNotification note;

    public CustomPage()
    {
        this.DefaultStyleKey = typeof(CustomPage);
    }

    protected override void OnApplyTemplate()
    {
        appbar = GetTemplateChild("appbar") as AppBar;
        note = GetTemplateChild("note") as InAppNotification;
    }

    public void ShowNotification()
    {
        if (appbar != null)
            appbar.IsOpen = true;
        note?.Show();
    }
}

...并在Themes/Generic.xaml 中定义一个自定义Style

<Style TargetType="local:CustomPage">
    <Setter Property="BottomAppBar">
        <Setter.Value>
            <AppBar Background="Transparent" x:Name="appbar" IsOpen="True">
                <uwp_toolkit:InAppNotification x:Name="note">
                    <StackPanel>
                        <TextBlock>HEADER!</TextBlock>
                        <TextBlock>Message</TextBlock>
                        <StackPanel Orientation="Horizontal">
                            <Button>OK</Button>
                            <Button>Cancel?</Button>
                        </StackPanel>
                    </StackPanel>
                </uwp_toolkit:InAppNotification>
            </AppBar>
        </Setter.Value>
    </Setter>
</Style>

.xaml.cs 基类有 .xaml.cs 文件。

编辑:由于BottomAppBar 不是模板的一部分,您需要等待访问其中的元素,直到它们被实际创建。只需在方法中执行此操作:

public sealed class CustomPage : Page
{
    public CustomPage()
    {
        this.DefaultStyleKey = typeof(CustomPage);
    }

    public void ShowNotification()
    {
        AppBar appBar = this.BottomAppBar;
        appBar.IsOpen = true;
        InAppNotification note = appBar.Content as InAppNotification;
        if(note != null)
            note.Show();
    }
}

【讨论】:

  • 您是否看到这在我上面的示例中是否偶然起作用?我提到我已经尝试过模板化控件。好奇这有什么不同
  • 刚刚测试过了。 noteappbar 始终为空
  • 另外,为了确认,我创建了一个测试模板化控件,它只有一个模板样式的简单文本框。有用。所以这里还有其他事情在进行中
  • 我已经复制了这个问题并将它放在 github 上,这样你就可以看到你的答案已实现github.com/DotNetRussell/UWP_Page_Inheritance_Bug
  • 虽然我很欣赏这项工作,但这真的与底部应用栏无关。它是关于能够定义作为消费者内容容器的自定义页面。我希望能够在主要内容字段中定义包含并围绕用户提供的内容的元素。目前我认为这对于页面是不可能的,尽管对于其他所有控件都是可能的。我认为这是平台中的一个错误
【解决方案2】:

问题是如果用Xaml创建自定义基本页面,它会强制转换为子页面的内容,如果子页面包含与基本页面不同的控件会抛出异常。更好的方法是在没有 xaml 的情况下创建基类,并在后面的代码中添加基页内容。更多代码请参考以下内容。

public class BasePage : Page
{

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {

        this.BottomAppBar = new AppBar()
        {
            Background = new SolidColorBrush(Colors.Transparent),
            IsOpen = false,
            Content = new InAppNotification
            {
                Content = new StackPanel
                {
                    Children =
                    {
                    new TextBlock{ Text = "HEADER!"},
                    new TextBlock{Text = "Message"},
                    new StackPanel
                    {
                        Orientation = Orientation.Horizontal,
                        Children=
                        {
                            new Button{Content = "ok"},
                            new Button {Content = "cancel"}
                        }
                    }

                    }
                }
            }
        };
        base.OnNavigatedTo(e);

    }
    public void ShowNotification()
    {
        this.BottomAppBar.IsOpen = true;
        InAppNotification note = this.BottomAppBar.Content as InAppNotification;
        if (note != null)
            note.Show();
    }
}

用法

public sealed partial class MainPage : BasePage
{
    public MainPage() 
    {        
        this.InitializeComponent();       
    }


    private void Button_Click(object sender, RoutedEventArgs e)
    {
        ShowNotification();
    }
}

Xaml

<local:BasePage
    x:Class="CustomPage.MainPage"
    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:local="using:CustomPage"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    >

    <Grid>
        <Button Click="Button_Click" Content="ClikMe" />
    </Grid>
</local:BasePage>

【讨论】:

  • 我同意这是一种解决方法,但在 CS 中生成控件只是为了塞入页面可能不是推荐的模式。我希望页面能够像我想从中派生的任何其他控件一样工作。
  • 我们只是在cs中初始化控件,它不会卡在页面中。如果你想让基本页面像控件一样工作,你可以参考@mm8 的回放,
  • 我只是不知道为什么不允许我像其他所有东西一样修改页面控件模板。
  • emm,这是设计使然,我认为xaml是独立的部分,可以直接继承
猜你喜欢
  • 1970-01-01
  • 2013-11-02
  • 1970-01-01
  • 2017-06-17
  • 2011-03-31
  • 2019-05-04
  • 2014-08-23
  • 1970-01-01
  • 2015-01-15
相关资源
最近更新 更多