2010-09-07 15 views
40

Parece que debería ser simple. Tengo un Page declarado en XAML de la manera normal (es decir, con "Agregar nuevo elemento ...") y tiene una propiedad personalizada. Me gustaría establecer esa propiedad en el XAML asociado a la página.Establecer una propiedad personalizada dentro de una página de WPF/Silverlight

Intentar hacer esto de la misma manera que establecería cualquier otra propiedad no funciona, por razones que entiendo pero que no sé cómo evitarlo. Solo para que tengamos algo concreto de qué hablar, aquí hay algunos (inválidos) XAML. Reduje todo lo posible, originalmente había atributos como el tamaño del diseñador, pero creo que son irrelevantes para lo que estoy tratando de hacer.

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

y el código subyacente correspondiente:

using System.Windows.Controls; 

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

    public string MyProperty { get; set; } 
    } 
} 

mensaje de error:

de error 1 La propiedad 'MyProperty' no existe en el espacio de nombres XML 'http://schemas.microsoft.com/winfx/2006/xaml/presentation'. Línea 4 Posición 7.

Ahora sé por qué esto está fallando: el elemento es de tipo Page y Page no tiene una propiedad llamada MyProperty. Eso solo se declara en TestPage ... especificado por el atributo x:Class, pero no por el elemento en sí. Hasta donde yo sé, esta configuración es requerida por el modelo de procesamiento XAML (es decir, la integración de Visual Studio, etc.).

Sospecho que podría manejar esto con una propiedad de dependencia, pero eso se siente como un exceso. También podría usar una propiedad existente (por ejemplo, DataContext) y luego copiar el valor en la propiedad personalizada en el código más tarde, pero sería bastante desagradable.

Lo anterior es un ejemplo de WPF, pero sospecho que se aplicarán las mismas respuestas en Silverlight. Estoy interesado en ambos, así que si publica una respuesta que sabe que funcionará en uno pero no en el otro, le agradecería que lo indicara en la respuesta :)

Estoy preparando patear a mí mismo cuando alguien publica una solución absolutamente trivial ...

+0

¿"MyProperty" en el elemento xaml 'Page' necesita un espacio de nombres xml? Tales como "' x: MyProperty' "? (No es eso literalmente, pero es similar). Dado que no está en ese espacio de nombres, ¿qué otros espacios de nombres está revisando? –

+1

@Filip: no creo que en realidad sea un duplicado de esa pregunta, que está hablando de propiedades adjuntas. El problema aquí es que la propiedad que intento establecer es efectivamente una propiedad de la clase * actual * en lugar de la declarada por el elemento. Podría estar equivocado, por supuesto. –

+4

Guau .. ¡Jon Skeet consiguió un voto cercano! ¿A qué viene el mundo? – Arcturus

Respuesta

30

Puede trabajar con una propiedad normal sin la propiedad Dependencia si crea una clase Base para su Página.

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> 

Y funciona a pesar de que MyProperty no es una Dependencia o Adjunto.

+0

Que perderá 'InitializeComponent()' –

+23

-1 por atreverse a responder una pregunta de jon skeet? –

+1

@Sudhir Good Job. – abhishek

0

Tendría que definir que es propiedad de acceso para acceder de esta manera.

3

Usted podría declarar su elemento <Page> a ser un elemento <TestPage> lugar:

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

Eso haría el truco, pero se pierde el InitializeComponent() y cosas de diseño estándar. Sin embargo, el modo de diseño parece funcionar perfectamente, pero no lo he probado exhaustivamente.

ACTUALIZACIÓN: Esto compila y ejecuta, pero en realidad no establece MyProperty. También pierdes la capacidad de vincular manejadores de eventos en XAML (aunque puede haber una manera de restaurar aquello que desconozco).

ACTUALIZACIÓN 2: muestra de Trabajo de @Fredrik Mörk que establece la propiedad, pero no es compatible con los controladores de eventos de enlace en XAML:

Código atrás:

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

Al intentar eso, aparece "La etiqueta TestPage no existe en el espacio de nombres XML 'http://schemas.microsoft.com/winfx/2006/xaml/presentation'" Línea 2 Posición 7. Hmm. También traté de darle un espacio de nombres clr, y eso tampoco parece funcionar :( –

+0

@ Håvard, que se compilará y ejecutará, pero la propiedad no tendrá el valor "Hola". –

+0

Disculpe, leve error de publicación Necesita 'xmlns: YourApp =" clr-namespace: YourApp "' y declare como ''. Actualizando la respuesta –

8

Tendría que convertirlo en attachable property como notó Pavel, entonces puede escribir algo como esto

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

Sin embargo, con sólo el código detrás:

Usted recibirá este error en su lugar:

La propiedad acoplable 'MyProperty' no se encontró en el tipo 'SkeetPage'.

La propiedad adjunta 'SkeetPage.MyProperty' no está definida en 'Página' o en una de sus clases base.


Editar

Desafortunadamente usted tiene que utilizar las propiedades de dependencia, es ella un ejemplo de trabajo

Página

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

el código detrás

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 pulsa el botón, verá "Testing ..." en un cuadro de mensaje.

+0

Esta es la respuesta correcta. – l33t

2

Su XAML es equivalente a la siguiente:

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

Esto es obviamente ilegal. El archivo XAML está siendo cargado por el método LoadComponent estático de la clase de aplicación, y the reference dice:

carga un archivo XAML que se encuentra en el identificador uniforme de recursos (URI) y la convierte en una instancia de el objeto que especifica el elemento raíz del archivo XAML.

Eso significa que solo puede establecer propiedades para el tipo especificado por el elemento raíz.Por lo tanto, debe crear una subclase de página y especificar esa subclase como el elemento raíz de su XAML.

1

Mi sugerencia sería un DependencyProperty con un defecto:

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 

Ver las propiedades de los controles como algo que exponer al mundo exterior para su uso.

Si desea utilizar su propia propiedad, puede utilizar ElementName o RelativeSource Fijaciones.

Acerca de lo excesivo, DependencyProperties van de la mano con DependencyObjects;)

Ninguna otra XAML necesario, el valor por defecto en el PropertyMetadata hará el resto.

Si realmente desea ponerlo en el XAML, busque la solución de la clase base, o los dioses no lo permiten, presente una propiedad conectable, que también se puede usar en cualquier otro control.

+0

Sugiero que realmente intentes esto, no funciona. – AnthonyWJones

+0

¿Por qué esto no funcionaría? No es necesario más XAML ... El valor predeterminado hará el resto ... – Arcturus

1

La respuesta se refiere a Silverlight.

No hay una forma simple y obvia de utilizar la propiedad plana de la manera que desee, tendrá que haber algún compromiso en el camino.

no funciona muy bien: -

Algunos sugieren una propiedad de dependencia. Eso no funcionará, sigue siendo una propiedad pública de Xaml POV. Una propiedad adjunta funcionará pero eso haría que trabajar con ella en código fuera feo.

Cerca, pero no de plátano: -

el XAML y la clase puede ser totalmente separados de esta manera: -

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

Código: -

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

} 

Sin embargo se pierde apoyo del diseñador. En particular, tendrá que crear los campos para mantener las referencias a los elementos con nombre y asignarlos usted mismo en su propia implementación de InitialiseComponent (OMI todos estos campos automáticos para los elementos con nombre no es necesariamente una buena cosa de todos modos). Además, el diseñador no creará un código de evento de forma dinámica para usted (aunque extrañamente parece saber cómo navegar a uno que haya creado manualmente), sin embargo, los eventos definidos en Xaml se conectarán en tiempo de ejecución.

mejor opción IMO: -

El mejor compromiso ya ha sido publicado por Abhishek, utilice una clase base cuña para mantener las propiedades. Mínimo esfuerzo, máxima compatibilidad.

1

Aunque traté de hacer lo mismo con una intención diferente.

La verdadera respuesta en realidad es: necesita la convención WPF para Set-methods realizada correctamente. Como se describe aquí: http://msdn.microsoft.com/en-us/library/ms749011.aspx#custom debe definir los métodos SetXxx y GetXxx si está a punto de definir una propiedad adjunta llamada Xxx.

Así ver este ejemplo de trabajo:

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

Y la parte de 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> 

Por cierto: Es necesario también para heredar DependencyObject

1

Esto funcionó para mí

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

Esto puede funcionar para la pregunta original (no lo intenté). Nota 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> 
Cuestiones relacionadas