46

This sounds like it should be simple. I have a Page declared in XAML in the normal way (i.e. with "Add new item...") and it has a custom property. I'd like to set that property in the XAML associated with the page.

Trying to do this the same way that I'd set any other property doesn't work, for reasons I understand but don't know how to work round. Just so we've got something concrete to talk about, here's some (invalid) XAML. I've reduced everything down as much as possible - originally there were attributes such as the designer size, but I believe those are irrelevant to what I'm trying to do.

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

and the corresponding code-behind:

using System.Windows.Controls;

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

    public string MyProperty { get; set; }
  }
}

Error message:

Error 1 The property 'MyProperty' does not exist in XML namespace 'http://schemas.microsoft.com/winfx/2006/xaml/presentation'. Line 4 Position 7.

Now I know why this is failing: the element is of type Page, and Page doesn't have a property called MyProperty. That's only declared in TestPage... which is specified by the x:Class attribute, but not by the element itself. As far as I'm aware, this configuration is required by the XAML processing model (i.e. the Visual Studio integration etc).

I suspect I could handle this with a dependency property, but that feels a little like overkill. I could also use an existing property (e.g. DataContext) and then copy the value into the custom property in code later, but that would be pretty ugly.

The above is a WPF example, but I suspect the same answers will apply in Silverlight. I'm interested in both - so if you post an answer which you know will work in one but not the other, I'd be grateful if you'd indicate that within the answer :)

I'm preparing to kick myself when someone posts an absolutely trivial solution...

Pang
  • 9,564
  • 146
  • 81
  • 122
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Does "MyProperty" in the xaml `Page` element need a xml namespace? Such as "`x:MyProperty`"? (Not that literally, but similar). Point being it's not in that namespace, so what other namespaces is it checking? – Neil Barnwell Sep 07 '10 at 10:32
  • 1
    @Filip: I don't believe it's actually a duplicate of that question, which is talking about attached properties. The problem here is that the property I'm trying to set is effectively a property of the *actual* class rather than the one declared by the element. I could be wrong, of course. – Jon Skeet Sep 07 '10 at 10:35
  • 4
    Wow.. Jon Skeet got a close vote! What is the world coming to?? – Arcturus Sep 07 '10 at 11:48
  • Mad dash to answer a Jon Skeet question! I had the exact same problem a while ago, this question helped a bit: http://stackoverflow.com/questions/225878/how-to-correctly-inherit-from-a-usercontrol-defined-in-xaml-in-silverlight – Chris S Sep 07 '10 at 12:02
  • Is there a reason you can't just set the value of the property in the constructor? – Quartermeister Sep 07 '10 at 12:05
  • @Quartermeister: Only that it would be slightly ugly to do so. I'm getting the value from a static resource, and it's just nicer to do that declaratively. – Jon Skeet Sep 07 '10 at 12:10

10 Answers10

33

You can work with normal property without Dependency property if you create a Base class for your Page.

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>

And it works even though MyProperty is not a Dependency or Attached.

Pang
  • 9,564
  • 146
  • 81
  • 122
abhishek
  • 2,975
  • 23
  • 38
  • That will loose `InitializeComponent()` – Filip Ekberg Sep 07 '10 at 10:57
  • @Filip Yes, Its My bad... Just was looking into it for a solution. :D – abhishek Sep 07 '10 at 11:11
  • @Filip: What do you mean, exactly? I'm trying this at the moment, and it's looking good so far... Just to be specific, I don't mind the small base class not having an `InitializeComponent()` method - but the derived class (`Window1` in the sample above) will still have initialization won't it? – Jon Skeet Sep 07 '10 at 11:14
  • @Jon, this is the same approach Håvard was aiming at. You will loose `InitializeComponent()`. See the comments on his post. You should have `InitializeComponent()` in `Window1`. However, it seems cleaner to just use Dependency Properties instead, which makes more sense. – Filip Ekberg Sep 07 '10 at 11:18
  • 3
    @Filip: It doesn't look like the same approach as Håvard's - and I still don't know *exactly* what you mean by "losing" `InitializeComponent()`. From which class? (There are two involved here.) It's still present in the derived class, and seems to work. – Jon Skeet Sep 07 '10 at 11:25
  • +1 I was just putting together my example but this pretty much ties it up anyway. – AnthonyWJones Sep 07 '10 at 11:27
  • @Jon, You are right, you will still have `InitializeComponent()`. But can you use event bindings in xaml? – Filip Ekberg Sep 07 '10 at 11:29
  • @Filip: Bindings to which events in particular? It all seems to be working fine at the moment... can you give a precise example of what might be failing? If I would only not be able to bind to events declared in the intermediate class, that would be a pity but probably not an issue for me right now. – Jon Skeet Sep 07 '10 at 11:30
  • @Filip I think here I can change the value of MyProperty anywhere. I changed the value in XAML, in Window1 or even in BaseWindow. I think I am not intended towards doing any designer stuffs in my BaseWindow class, the BaseWindow will hold only the Base properties. – abhishek Sep 07 '10 at 11:30
  • @Jon: you are correct. A modification to Håvard's approach that might be used is to implement `InitializeComponent()` yourself if you don't have too many Named elements. However the benefits of getting much better support from the designer IMO out-weighs the cost of a small base class shim to hold the properties. – AnthonyWJones Sep 07 '10 at 11:31
  • @Jon, I am referring to the comment made on Håvard's answer. I did not try it myself though. But I do guess he means ``. Is there a particular reason to why you would not want to use Dependency Properties instead? Is this not what they should be used to? You can always create a snippet :) – Filip Ekberg Sep 07 '10 at 11:35
  • @Filip Well, I personally don't declare a Dependency property when I don't want to have support for Styles, animations, etc. For simple datastorage I think normal properties are enough. implementing INotifyPropertyChanged will add more life to that property. – abhishek Sep 07 '10 at 11:45
  • @Filip: Håvard's answer looks different enough to me that I think it's unlikely to be a problem. It's not using `x:Class` which I suspect is the problem. As for why it shouldn't be a dependency property: as far as I can see, that makes it signficantly more complicated, and it just doesn't *feel* like a dependency property. It feels like a normal property to me. It's something my page will always need, unlike (say) Grid dependency properties which only make sense for controls in a grid. But I'm still relatively new at XAML... I may change my mind :) – Jon Skeet Sep 07 '10 at 11:46
  • @Jon, I think that if you want to access a property like that On the Page In the Design, you have a dependency between your Model / View / Presenter which imho is what Dependency Properties are there for. It is more code than a "normal" property, but that also gives your better interaction with your UI. – Filip Ekberg Sep 07 '10 at 11:57
  • If BaseWindow calls InitializeComponent() in its constructor, how do you lose it? – Chris S Sep 07 '10 at 12:05
  • This is what I meant in my latter comment on my own response. +1 for it. The problem of my example is indeed that it doesn't use `x:Class`, and to get both worlds, you need a class in between, like this answer suggests. – Håvard S Sep 07 '10 at 12:06
  • So x:Class redeems the event handlers? – Chris S Sep 07 '10 at 12:13
  • @Filip: I'm using MVVM rather than MVP, but this property is actually for a ViewModel factory. The only code which needs to know about this property is the page itself. I *could* just fetch it straight from the application resources when I want it, but this way feels cleaner to me. It's working fine for me at the moment. – Jon Skeet Sep 07 '10 at 12:14
  • @Chris: x:Class results in a partial class being created with contains a dynamically created `InitializeComponent` method. In that method there is a call to `LoadComponent` which applies the Xaml to the page being constructed. Its this `LoadComponent` which will attach events declared in the Xaml to the appropriate event method defined in the Page. See my answer below. – AnthonyWJones Sep 07 '10 at 12:34
  • @Anthony this might explain VS2010 getting its knickers in a twist when I subclass UserControls, I haven't been using x:Class because of the recursion problem -all the intellisense vanishes and I have to restart VS. – Chris S Sep 07 '10 at 13:11
  • @Anthony is correct. If you declare an `x:Class` the hidden partial class (whatever.xaml.g.cs) will be generated by... something. Whether its the compiler or its the WPF project system that does it I'm not exactly sure. Too much magic in the whole process, imho. –  Sep 07 '10 at 15:12
6

You would need to make it an attachable property as Pavel noted, then you can write something like this

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

However, with only this code-behind, you will get this error instead:

The attachable property 'MyProperty' was not found in type 'SkeetPage'.

The attached property 'SkeetPage.MyProperty' is not defined on 'Page' or one of its base classes.


Edit

Unfortunately, you have to use Dependency Properties. Here's a working example

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>

Code-behind

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

If you press the button, you will see "Testing..." in a MessageBox.

Pang
  • 9,564
  • 146
  • 81
  • 122
Filip Ekberg
  • 36,033
  • 20
  • 126
  • 183
3

You could declare your <Page> element to be a <TestPage> element instead:

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

That would do the trick, but you lose InitializeComponent() and the standard designer stuff. Design mode still seems to work flawlessly, though, but I haven't extensively tested this.

UPDATE: This compiles and runs, but does not actually set MyProperty. You also lose the ability to bind event handlers in XAML (although there may be a way to restore that which I am unaware of).

UPDATE 2: Working sample from @Fredrik Mörk which sets the property, but does not support binding event handlers in XAML:

Code-behind:

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>
Pang
  • 9,564
  • 146
  • 81
  • 122
Håvard S
  • 23,244
  • 8
  • 61
  • 72
  • Trying that, I get "The tag TestPage does not exist in XML namespace 'http://schemas.microsoft.com/winfx/2006/xaml/presentation'" Line 2 Position 7. Hmm. I've tried giving it a clr-namespace as well, and that doesn't seem to work either :( – Jon Skeet Sep 07 '10 at 10:42
  • @Håvard, That will compile and run but the Property does not have the value "Hello". – Filip Ekberg Sep 07 '10 at 10:43
  • Sorry, slight mispost. You need `xmlns:YourApp="clr-namespace:YourApp"` and declare as ``. Updating the answer. – Håvard S Sep 07 '10 at 10:44
  • While working, this solution also removes the possibility to bind event handlers in XAML, which may be an inconvenience. – Fredrik Mörk Sep 07 '10 at 10:46
  • @Filip Ekberg Right you are. @Fredrik Mörk It does so, yes. Uh well, it sort of seemed like it could work, but I guess I was proven wrong. :) – Håvard S Sep 07 '10 at 10:49
  • 1
    @Håvard: you are not quite wrong. It *does* work, but comes with some drawbacks. – Fredrik Mörk Sep 07 '10 at 10:52
  • @Fredrik Mörk The property is not set for me, so no, it does not work. Please share your code if it does work for you, so we can all be enlightened. :) – Håvard S Sep 07 '10 at 10:53
  • @Håvard: it worked fine for me. With your permission, I'll edit it into your answer. – Fredrik Mörk Sep 07 '10 at 11:03
  • You loose the property assignment __because__ you don't have a `InitialiseComponent`. You don't have `InitialiseComponent` because you don't have an `x:Class` attribute hence no partial class is created. Of course if try to add this attribute you end up with a circular reference. – AnthonyWJones Sep 07 '10 at 11:03
  • @AnthonyWJones Right you are, you are only able to set on the base class when using `x:Class` to define the class. So, you're either left with using XAML purely for instantiating, or using attachable properties, or putting a third class "in between" in the hierarchy which you declare as your `x:Class` and which contains the desired properties. – Håvard S Sep 07 '10 at 11:16
  • 1
    XAML: removing all OO concepts wherever possible – Chris S Sep 07 '10 at 12:11
2

Your XAML is equivalent of the following:

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

This is obviously illegal. The XAML-file is being loaded by the static LoadComponent method of the Application class, and the reference says:

Loads a XAML file that is located at the specified uniform resource identifier (URI) and converts it to an instance of the object that is specified by the root element of the XAML file.

That means that you can only set properties for the type specified by the root element. So you need to subclass Page and specify that subclass as the root element of you XAML.

Alf Kåre Lefdal
  • 643
  • 1
  • 6
  • 27
2

This worked for me

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

So this may work for the original question (didn't try). Note 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>
bab
  • 21
  • 2
1

My suggestion would be a DependencyProperty with a default:

    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

See the properties of controls as something you expose to the outside world to use.

If you wish to use your own property, you can use either ElementName or RelativeSource Bindings.

About the overkill thing, DependencyProperties go hand in hand with DependencyObjects ;)

No further XAML needed, the default in the PropertyMetadata will do the rest.

If you really wish to put it in the XAML, go for the base class solution, or gods forbid, introduce an attachable property, which can be used on any other control as well.

Arcturus
  • 26,677
  • 10
  • 92
  • 107
1

Answer relates to Silverlight.

There is no simple obvious way to use plain property in the way you want, there will have to be some compromise along the way.

Doesn't really work:-

Some suggest a dependency property. That won't work, its still a public property from Xaml POV. An attached property will work but that would make working with it in code ugly.

Close but no banana:-

The Xaml and the class can be fully separated like this:-

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

Code:-

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

}

However you lose some support from the designer. Notably you will have to create the fields to hold references to named elements and assign them yourself in your own implementation of InitialiseComponent (IMO all these automatic fields for named items isn't necessarily a good thing anyway). Also the designer won't create event code dynamically for you (although strangely it seems to know how to navigate to one you have manually created) however events defined in Xaml will be wired up at runtime.

IMO best option:-

The best compromise has already been posted by abhishek, use a shim base class to hold the properties. Minimul effort, maximum compatibility.

AnthonyWJones
  • 187,081
  • 35
  • 232
  • 306
1

I just tried to do the same with some different intent, though.

The real answer actually is: You need the WPF convention for Set-methods done right. As outlined here: http://msdn.microsoft.com/en-us/library/ms749011.aspx#custom you have to define the SetXxx and GetXxx methods if you are about to definde an attached property named Xxx.

So see this working example:

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

And the WPF part:

<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: You need also to inherit DependencyObject

RobKop
  • 31
  • 1
0

You would need to define it is attachable property to access it like this.

Pavel
  • 9
  • 1
0

You can set the property with a style:

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

But it only works for dependency properties!

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); }
}
Pang
  • 9,564
  • 146
  • 81
  • 122
l33t
  • 18,692
  • 16
  • 103
  • 180