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

时间:2010-09-07 10:27:17

标签: wpf silverlight xaml

这听起来应该很简单。我在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名称空间中不存在属性“MyProperty”   'http://schemas.microsoft.com/winfx/2006/xaml/presentation'。第4行位置7.

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

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

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

当有人发布一个非常简单的解决方案时,我正准备踢自己......

10 个答案:

答案 0 :(得分:30)

如果为页面创建Base类,则可以使用不带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不是Dependency或Attached,它仍然有效。

答案 1 :(得分:8)

你需要将其设为attachable property,如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>

然而,只有这个代码背后:

您将收到此错误:

  

可附加物业'MyProperty'   在'SkeetPage'类型中找不到。

     

附属物   '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中看到“Testing ...”。

答案 2 :(得分:3)

您可以将<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:来自@FredrikMö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>

答案 3 :(得分:2)

您的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方法加载,the reference表示:

  

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

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

答案 4 :(得分:1)

我的建议是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绑定。

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

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

如果你真的希望把它放在XAML中,那就选择基类解决方案,或者上帝禁止,引入一个可附加的属性,也可以用在任何其他控件上。

答案 5 :(得分:1)

答案与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中定义的事件将在运行时连接。

IMO最佳选择: -

最好的妥协已经由abhishek发布,使用垫片基类来保存属性。最小的努力,最大的兼容性。

答案 6 :(得分:1)

我只是尝试用不同的意图做同样的事情。

真正的答案实际上是:你需要正确完成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>

BTW:您还需要继承DependencyObject

答案 7 :(得分:1)

这对我有用

<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>

答案 8 :(得分:0)

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

答案 9 :(得分:0)

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

<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); }
}