繁体   English   中英

在 WPF/Silverlight 页面中设置自定义属性

[英]Setting a custom property within a WPF/Silverlight page

这听起来应该很简单。 我有一个以正常方式在 XAML 中声明的Page (即使用“添加新项目...”)并且它有一个自定义属性。 我想在与页面关联的 XAML 中设置该属性。

尝试以与设置任何其他属性相同的方式执行此操作不起作用,原因我理解但不知道如何解决。 为了让我们有一些具体的讨论,这里有一些(无效的)XAML。 我已经尽可能地减少了一切——最初有诸如设计师尺寸之类的属性,但我相信这些与我想要做的事情无关。

<Page x:Class="WpfSandbox.TestPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      MyProperty="MyPropertyValue">
</Page>

以及相应的代码隐藏:

using System.Windows.Controls;

namespace WpfSandbox {
  public partial class TestPage : Page {
    public TestPage() {
      InitializeComponent();
    }

    public string MyProperty { get; set; }
  }
}

错误信息:

错误 1 ​​XML 命名空间“http://schemas.microsoft.com/winfx/2006/xaml/presentation”中不存在属性“MyProperty”。 第 4 行位置 7。

现在我知道为什么会失败:元素的类型为Page ,而Page没有名为MyProperty的属性。 这仅在TestPage ... 中声明,它由x:Class属性指定,而不是由元素本身指定。 据我所知,XAML 处理模型(即 Visual Studio 集成等)需要此配置。

我怀疑我可以使用依赖属性处理这个问题,但这感觉有点矫枉过正。 我也可以使用现有的属性(例如DataContext ),然后稍后将值复制到代码中的自定义属性中,但这会非常难看。

以上是一个 WPF 示例,但我怀疑在 Silverlight 中也适用相同的答案。 我对两者都感兴趣 - 所以如果你发布一个你知道可以在其中一个而不是另一个中工作的答案,如果你能在答案中指出这一点,我将不胜感激:)

当有人发布一个绝对微不足道的解决方案时,我正准备踢自己......

如果为页面创建基类,则可以使用没有 Dependency 属性的普通属性。

public class BaseWindow : Window
{
   public string MyProperty { get; set; }
}
<local:BaseWindow x:Class="BaseWindowSample.Window1" x:Name="winImp"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:BaseWindowSample" 
    MyProperty="myproperty value"
    Title="Window1" Height="300" Width="300">

</local:BaseWindow>

即使 MyProperty 不是依赖项或附加项,它也能工作。

正如 Pavel 指出的那样,您需要将其设置为可附加属性,然后您可以编写这样的内容

<Page x:Class="JonSkeetTest.SkeetPage"
      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:JonSkeetTest="clr-namespace:JonSkeetTest" mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"
       JonSkeetTest:SkeetPage.MyProperty="testar"
    Title="SkeetPage">
    <Grid>
        
    </Grid>
</Page>

但是,仅使用此代码隐藏,您将收到此错误:

在“SkeetPage”类型中找不到可附加属性“MyProperty”。

附加属性“SkeetPage.MyProperty”未在“Page”或其基类之一上定义。


编辑

不幸的是,您必须使用依赖属性。 这是一个工作示例

页面

<Page x:Class="JonSkeetTest.SkeetPage"
      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:JonSkeetTest="clr-namespace:JonSkeetTest" mc:Ignorable="d" 
      JonSkeetTest:SkeetPage.MyProperty="Testing.."
      d:DesignHeight="300" d:DesignWidth="300"
    Title="SkeetPage">
   
    <Grid>
        <Button Click="ButtonTest_Pressed"></Button>
    </Grid>
</Page>

代码隐藏

using System.Windows;
using System.Windows.Controls;

namespace JonSkeetTest
{
    public partial class SkeetPage
    {
        public SkeetPage()
        {
            InitializeComponent();
        }

        public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register(
          "MyProperty",
          typeof(string),
          typeof(Page),
          new FrameworkPropertyMetadata(null,
              FrameworkPropertyMetadataOptions.AffectsRender
          )
        );

        public static void SetMyProperty(UIElement element, string value)
        {
            element.SetValue(MyPropertyProperty, value);
        }
        public static string GetMyProperty(UIElement element)
        {
            return element.GetValue(MyPropertyProperty).ToString();
        }

        public string MyProperty
        {
            get { return GetValue(MyPropertyProperty).ToString(); }
            set { SetValue(MyPropertyProperty, value); }
        }

        private void ButtonTest_Pressed(object sender, RoutedEventArgs e)
        {
            MessageBox.Show(MyProperty);
        }
    }
}

如果您按下按钮,您将在 MessageBox 中看到“正在测试...”。

您可以将<Page>元素声明为<TestPage>元素:

<YourApp:TestPage 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  xmlns:YourApp="clr-namespace:YourApp"
  MyProperty="Hello">
</YourApp:TestPage>

这会奏效,但你会失去InitializeComponent()和标准设计器的东西。 不过,设计模式似乎仍然完美无缺,但我还没有对此进行广泛的测试。

更新:这会编译并运行,但实际上并未设置MyProperty 您还失去了在 XAML 中绑定事件处理程序的能力(尽管可能有一种方法可以恢复我不知道的内容)。

更新 2:来自 @Fredrik Mörk 的工作示例,它设置了属性,但不支持 XAML 中的绑定事件处理程序:

代码隐藏:

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        protected override void OnActivated(EventArgs e)
        {
            this.Title = MyProperty;
        }      

        public string MyProperty { get; set; }
    }
}

XAML:

<WpfApplication1:MainWindow
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:WpfApplication1="clr-namespace:WpfApplication1" 
    Title="MainWindow" 
    Height="350" 
    Width="525"
    MyProperty="My Property Value"> 
</WpfApplication1:MainWindow>

您的 XAML 等效于以下内容:

<Page x:Class="SkeetProblem.TestPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.MyProperty>MyPropertyValue</Page.MyProperty> 
</Page>

这显然是违法的。 XAML 文件是由 Application 类的静态 LoadComponent 方法加载的,参考文献说:

加载位于指定的统一资源标识符 (URI) 的 XAML 文件,并将其转换为由 XAML 文件的根元素指定的对象的实例。

这意味着您只能为根元素指定的类型设置属性。 因此,您需要对 Page 进行子类化并将该子类指定为 XAML 的根元素。

这对我有用

<Window x:Class="WpfSandbox.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfSandbox"        
    xmlns:src="clr-namespace:WpfSandbox" 
    Title="MainWindow" Height="350" Width="525"
    src:MainWindow.SuperClick="SuperClickEventHandler">
</Window>

所以这可能适用于原始问题(没有尝试)。 注意 xmlns:src。

<Page x:Class="WpfSandbox.TestPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:WpfSandbox"        
  xmlns:src="clr-namespace:WpfSandbox" 
  src:TestPage.MyProperty="MyPropertyValue">
</Page>

我的建议是使用默认值的DependencyProperty

    public int MyProperty
    {
        get { return (int)GetValue(MyPropertyProperty); }
        set { SetValue(MyPropertyProperty, value); }
    }

    public static readonly DependencyProperty MyPropertyProperty =
        DependencyProperty.Register("MyProperty", typeof(int), typeof(MyClass), 
               new PropertyMetadata(1337)); //<-- Default goes here

将控件的属性视为您向外界公开以供使用的内容。

如果您希望使用自己的属性,则可以使用ElementNameRelativeSource Bindings。

关于矫枉过正的事情, DependencyPropertiesDependencyObjects齐头并进;)

不需要进一步的 XAML, PropertyMetadata的默认值将完成剩下的工作。

如果您真的希望将它放在 XAML 中,请使用基类解决方案,或者上帝禁止,引入一个可附加属性,它也可以用于任何其他控件。

答案与 Silverlight 有关。

没有简单明显的方法可以按照您想要的方式使用普通属性,在此过程中必须做出一些妥协。

真的行不通:-

有些人建议使用依赖属性。 这行不通,它仍然是 Xaml POV 的公共财产。 附加属性将起作用,但这会使在代码中使用它变得丑陋。

关闭但没有香蕉:-

Xaml 和类可以像这样完全分离:-

<local:PageWithProperty
           xmlns:local="clr-namespace:StackoverflowSpikes"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
    Message="Hello World"
    Loaded="PageWithProperty_Loaded"
    Title="Some Title"
           >
    <Grid x:Name="LayoutRoot">
        <TextBlock Text="{Binding Parent.Message, ElementName=LayoutRoot}" />
    </Grid>
</local:PageWithProperty>

代码:-

public class PageWithProperty : Page
{

        internal System.Windows.Controls.Grid LayoutRoot;

        private bool _contentLoaded;

        public void InitializeComponent()
        {
            if (_contentLoaded) {
                return;
            }
            _contentLoaded = true;
            System.Windows.Application.LoadComponent(this, new System.Uri("/StackoverflowSpikes;component/PageWithProperty.xaml", System.UriKind.Relative));
            this.LayoutRoot = ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot")));
         }

    public PageWithProperty()
    {
        InitializeComponent();
    }

    void PageWithProperty_Loaded(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("Hi");
    }
    public string Message {get; set; }

}

然而,你失去了设计师的一些支持。 值得注意的是,您必须创建字段来保存对命名元素的引用,并在您自己的InitialiseComponent实现中自己分配它们(IMO 所有这些命名项的自动字段无论如何都不一定是一件好事)。 此外,设计器不会为您动态创建事件代码(尽管奇怪的是它似乎知道如何导航到您手动创建的代码),但是在 Xaml 中定义的事件将在运行时连接起来。

海事组织最佳选择:-

最好的折衷方案已经由 abhishek 发布,使用 shim 基类来保存属性。 最小的努力,最大的兼容性。

不过,我只是试图以不同的意图做同样的事情。

真正的答案实际上是:您需要正确完成 Set-methods 的 WPF 约定。 如此处所述: http : //msdn.microsoft.com/en-us/library/ms749011.aspx#custom如果您要定义名为 Xxx 的附加属性,则必须定义 SetXxx 和 GetXxx 方法。

所以看这个工作示例:

public class Lokalisierer : DependencyObject
{
    public Lokalisierer()
    {
    }

    public static readonly DependencyProperty LIdProperty = 
        DependencyProperty.RegisterAttached("LId", 
                                            typeof(string), 
                                            typeof(Lokalisierer),
                                            new FrameworkPropertyMetadata( 
                                                  null,
                                                     FrameworkPropertyMetadataOptions.AffectsRender | 
                                                     FrameworkPropertyMetadataOptions.AffectsMeasure,
                                                     new PropertyChangedCallback(OnLocIdChanged)));

    private static void OnLocIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    // on startup youll be called here
    }

    public static void SetLId(UIElement element, string value)
    {
      element.SetValue(LIdProperty, value);
    }
    public static string GetLId(UIElement element)
    {
      return (string)element.GetValue(LIdProperty);
    }


    public string LId
    {
        get{    return (string)GetValue(LIdProperty);   }
        set{ SetValue(LIdProperty, value); }
    }
}

和 WPF 部分:

<Window x:Class="LokalisierungMitAP.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:LokalisierungMitAP"
Title="LokalisierungMitAP" Height="300" Width="300"
>
<StackPanel>
    <Label  me:Lokalisierer.LId="hhh">Label1</Label>
   </StackPanel>

顺便说一句:您还需要继承 DependencyObject

您需要定义它是可附加的属性才能像这样访问它。

您可以使用样式设置属性:

<Page.Style>
    <Style TargetType="{x:Type wpfSandbox:TestPage}">
        <Setter Property="MyProperty" Value="This works" />
    </Style>
</Page.Style>

但它只适用于依赖属性!

public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register(
    nameof(MyProperty), typeof(string), typeof(Page),
    new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));

public string MyProperty
{
    get { return (string)GetValue(MyPropertyProperty); }
    set { SetValue(MyPropertyProperty, value); }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM