Définition d'une propriété personnalisée dans une page WPF / Silverlight

Cela semble être simple. J'ai un Page déclaré en XAML de la manière normale (c'est-à-dire avec " Ajouter un nouvel élément...") et il a une propriété personnalisée. Je voudrais définir cette propriété dans le XAML associé à la page.

Essayer de le faire de la même manière que je définirais n'importe quelle autre propriété ne fonctionne pas, pour des raisons que je comprends mais je ne sais pas comment travailler. Juste pour que nous ayons quelque chose de concret à parler, voici un XAML (invalide). J'ai réduit tout autant que possible-à l'origine, il y avait des attributs tels que la taille du concepteur, mais je crois que ceux-ci ne sont pas pertinents pour ce que j'essaie de faire.

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

Et le code correspondant-derrière:

using System.Windows.Controls;

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

    public string MyProperty { get; set; }
  }
}

Message d'Erreur:

Erreur 1 la propriété 'MyProperty' n'existe pas dans L'espace de noms XML "http://schemas.microsoft.com/winfx/2006/xaml/presentation '. Ligne 4 Position 7.

Maintenant je sais pourquoi c'est un échec: l'élément est de type Page, et Page n'a pas de propriété appelée MyProperty. C'est seulement déclaré dans TestPage... qui est spécifié par l'attribut x:Class, mais pas par l'élément lui-même. Pour autant que je sache, cette configuration est requise par le modèle de traitement XAML (c'est-à-dire L'intégration de Visual Studio, etc.).

Je soupçonne que je pourrais gérer cela avec une propriété de dépendance, mais cela ressemble un peu à un excès. Je pourrais également utiliser une propriété existante (par exemple DataContext), puis Copier la valeur dans la propriété personnalisée dans le code plus tard, mais ce serait plutôt moche.

Ce qui précède est un exemple WPF, mais je soupçonne que les mêmes réponses s'appliqueront dans Silverlight. Je suis intéressé par les deux - donc si vous postez une réponse qui, vous le savez, fonctionnera dans l'une mais pas dans l'autre, je vous serais reconnaissant si vous l'indiquiez dans la réponse:)

Je me prépare à me lancer quand quelqu'un poste une solution absolument triviale...

41
demandé sur Jon Skeet 2010-09-07 14:27:17

9 réponses

Vous pouvez travailler avec une propriété normale sans propriété de dépendance si vous créez une classe de base pour votre 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>

Et cela fonctionne même si MyProperty n'est pas une dépendance ou attaché.

30
répondu abhishek 2010-09-07 10:54:17

Vous auriez besoin de faire un biens saisissables Pavel noté, alors vous pouvez écrire quelque chose comme ceci

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

Cependant avec seulement ce code derrière:

, Vous obtiendrez cette erreur à la place:

La propriété attachable 'MyProperty' n'a pas été trouvé dans le type 'SkeetPage'.

La propriété jointe "SkeetPage.MyProperty ' n'est pas défini sur 'Page' ou sur l'une de ses bases classe.


Modifier

Malheureusement, vous devez utiliser les propriétés de dépendance, c'est un exemple de travail

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>

Le Code de

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

Si vous appuyez sur le bouton, vous verrez " test..."dans une MessageBox.

8
répondu Filip Ekberg 2017-05-23 10:31:15

Vous pouvez déclarer votre élément <Page> comme étant un élément <TestPage> à la place:

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

Cela ferait l'affaire, mais vous perdez InitializeComponent() et les trucs de concepteur standard. Le mode de conception semble toujours fonctionner parfaitement, mais je n'ai pas testé cela de manière approfondie.

UPDATE: cela compile et s'exécute, mais ne définit pas réellement MyProperty. Vous perdez également la capacité de lier les gestionnaires d'événements dans XAML (bien qu'il puisse y avoir un moyen de restaurer ce que je ne connais pas).

Mise à jour 2: exemple de travail de @ Fredrik Mörk qui définit la propriété, mais ne prend pas en charge les gestionnaires d'événements de liaison en XAML:

Code derrière:

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
répondu Håvard S 2010-09-07 11:14:06

Votre XAML est équivalent à ce qui suit:

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

C'est évidemment illégal. Le fichier XAML est chargé par la méthode statique LoadComponent de la classe Application, et la référence dit:

Charge un fichier XAML situé à l'URI (uniform resource identifier) spécifié et le convertit en une instance de l'objet spécifié par l'élément racine du fichier XAML.

Cela signifie que vous ne pouvez définir que des propriétés pour type spécifié par l'élément racine. Vous devez donc sous-classer la Page et spécifier cette sous-classe en tant qu'élément racine de votre XAML.

2
répondu Alf Kåre Lefdal 2010-09-07 11:34:54

La réponse concerne Silverlight.

Il N'y a pas de moyen simple et évident d'utiliser la propriété ordinaire comme vous le souhaitez, il devra y avoir un compromis en cours de route.

Ça ne marche pas vraiment:-

Certains suggèrent une propriété de dépendance. Cela ne fonctionnera pas, c'est toujours une propriété publique de XAML POV. Une propriété attachée fonctionnera mais cela rendrait le travail avec elle dans le code laid.

Proche mais pas de banane:-

Le Xaml et la classe peuvent être entièrement séparé comme ceci: -

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

}

Cependant, vous perdez un peu de soutien du concepteur. Notamment, vous devrez créer les champs pour contenir des références aux éléments nommés et les assigner vous-même dans votre propre implémentation de InitialiseComponent (IMO tous ces champs automatiques pour les éléments nommés n'est pas nécessairement une bonne chose de toute façon). De plus, le concepteur ne créera pas de code d'événement dynamiquement pour vous (bien qu'étrangement, il semble savoir comment naviguer vers celui que vous avez créé manuellement) cependant, les événements définis dans Xaml seront câblés au moment de l'exécution.

OMI meilleure option:-

Le meilleur compromis a déjà été posté par abhishek, utilisez une classe de base shim pour contenir les propriétés. Effort minimal, compatibilité maximale.

1
répondu AnthonyWJones 2010-09-07 11:52:15

MA suggestion serait un DependencyProperty avec un défaut:

    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

Voyez les propriétés des contrôles comme quelque chose que vous exposez au monde extérieur à utiliser.

Si vous souhaitez utiliser votre propre propriété, que vous pouvez utiliser ElementName ou RelativeSource Liaisons.

À propos de la chose exagérée, DependencyProperties aller de pair avecDependencyObjects ;)

Aucun autre XAML nécessaire, la valeur par défaut dans le PropertyMetadata fera le reste.

Si vous souhaitez vraiment le mettre dans le XAML, optez pour la solution de classe de base, ou dieux interdire, introduire une propriété attachable, qui peut être utilisé sur tout autre contrôle ainsi.

1
répondu Arcturus 2010-09-07 12:00:42

J'ai juste essayé de faire la même chose avec une intention différente, cependant.

La vraie réponse est en fait: vous avez besoin de la convention WPF pour Set-methods bien faite. Comme indiqué ici: http://msdn.microsoft.com/en-us/library/ms749011.aspx#custom vous devez définir les méthodes SetXxx et Getxxx si vous êtes sur le point de définir une propriété attachée nommée Xxx.

Donc, voir cet exemple de travail:

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

Et la partie 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: vous devez également hériter DependencyObject

1
répondu RobKop 2010-10-28 21:50:24

Cela a fonctionné pour moi

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

Donc cela peut fonctionner pour la question originale (n'a pas essayé). 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>
1
répondu bab 2011-09-12 06:24:14

Vous devez définir la propriété est attachable pour y accéder comme ceci.

0
répondu Pavel 2010-09-07 10:37:20