Setting a custom property within a WPF/Silverlight page
Asked Answered
O

10

45

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

Oliphant answered 7/9, 2010 at 10:27 Comment(6)
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?Lungfish
@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.Oliphant
Wow.. Jon Skeet got a close vote! What is the world coming to??Cardiogram
Mad dash to answer a Jon Skeet question! I had the exact same problem a while ago, this question helped a bit: #226378Septuplicate
Is there a reason you can't just set the value of the property in the constructor?Almatadema
@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.Oliphant
A
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.

Aeneous answered 7/9, 2010 at 10:54 Comment(21)
That will loose InitializeComponent()Snowwhite
@Filip Yes, Its My bad... Just was looking into it for a solution. :DAeneous
@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?Oliphant
@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.Snowwhite
@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.Oliphant
+1 I was just putting together my example but this pretty much ties it up anyway.Mosora
@Jon, You are right, you will still have InitializeComponent(). But can you use event bindings in xaml?Snowwhite
@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.Oliphant
@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.Aeneous
@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.Mosora
@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 :)Snowwhite
@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.Aeneous
@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 :)Oliphant
@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.Snowwhite
If BaseWindow calls InitializeComponent() in its constructor, how do you lose it?Septuplicate
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.Autoradiograph
So x:Class redeems the event handlers?Septuplicate
@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.Oliphant
@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.Mosora
@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.Septuplicate
@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.Tangible
S
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.

Snowwhite answered 7/9, 2010 at 10:48 Comment(0)
S
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>
Saphena answered 7/9, 2010 at 10:37 Comment(11)
Trying that, I get "The tag TestPage does not exist in XML namespace '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 :(Oliphant
@Håvard, That will compile and run but the Property does not have the value "Hello".Snowwhite
Sorry, slight mispost. You need xmlns:YourApp="clr-namespace:YourApp" and declare as <YourApp:TestPage>. Updating the answer.Autoradiograph
While working, this solution also removes the possibility to bind event handlers in XAML, which may be an inconvenience.Succor
@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. :)Autoradiograph
@Håvard: you are not quite wrong. It does work, but comes with some drawbacks.Succor
@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. :)Autoradiograph
@Håvard: it worked fine for me. With your permission, I'll edit it into your answer.Succor
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.Mosora
@Mosora 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.Autoradiograph
XAML: removing all OO concepts wherever possibleSeptuplicate
B
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.

Bezant answered 7/9, 2010 at 11:23 Comment(0)
R
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>
Rains answered 12/9, 2011 at 6:19 Comment(0)
C
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.

Cardiogram answered 7/9, 2010 at 11:44 Comment(1)
Why would this not work? No further XAML needed... The default will do the rest..Cardiogram
M
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.

Mosora answered 7/9, 2010 at 11:52 Comment(0)
P
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

Pulsar answered 28/10, 2010 at 21:50 Comment(0)
T
0

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

Transmitter answered 7/9, 2010 at 10:37 Comment(0)
L
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); }
}
Leyba answered 10/10, 2019 at 14:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.