views:

1146

answers:

9

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

+1  A: 

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

Pavel
+2  A: 

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>
Håvard S
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
@Håvard, That will compile and run but the Property does not have the value "Hello".
Filip Ekberg
Sorry, slight mispost. You need `xmlns:YourApp="clr-namespace:YourApp"` and declare as `<YourApp:TestPage>`. Updating the answer.
Håvard S
While working, this solution also removes the possibility to bind event handlers in XAML, which may be an inconvenience.
Fredrik Mörk
@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
@Håvard: you are not quite wrong. It *does* work, but comes with some drawbacks.
Fredrik Mörk
@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
@Håvard: it worked fine for me. With your permission, I'll edit it into your answer.
Fredrik Mörk
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
@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
XAML: removing all OO concepts wherever possible
Chris S
+3  A: 

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

Unfortunatly you have to use Dependency Properties, her'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>

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

Filip Ekberg
+12  A: 

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.

abhishek
That will loose `InitializeComponent()`
Filip Ekberg
-1 for daring to answer a jon skeet question?
Sudhir Jonathan
@Sudhir Good Job.
abhishek
@Filip Yes, Its My bad... Just was looking into it for a solution. :D
abhishek
@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
@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
@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
+1 I was just putting together my example but this pretty much ties it up anyway.
AnthonyWJones
@Jon, You are right, you will still have `InitializeComponent()`. But can you use event bindings in xaml?
Filip Ekberg
@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
@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
@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
@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 `<button Click="ButtonOne_Pressed" />`. 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
@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
@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
@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
If BaseWindow calls InitializeComponent() in its constructor, how do you lose it?
Chris S
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
So x:Class redeems the event handlers?
Chris S
@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
@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
@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
@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.
Will
+1  A: 

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"&gt;
    <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
A: 

You need to use DependencyProperty

portland
A: 

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
I suggest you actually try this, it doesn't work.
AnthonyWJones
Why would this not work? No further XAML needed... The default will do the rest..
Arcturus
+1  A: 

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

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