Установка пользовательского свойства на странице WPF / Silverlight - PullRequest
41 голосов
/ 07 сентября 2010

Похоже, все должно быть просто. У меня есть Page, объявленный в XAML обычным способом (то есть с "Добавить новый элемент ..."), и у него есть пользовательское свойство. Я хотел бы установить это свойство в XAML, связанном со страницей.

Попытка сделать это так же, как я установил бы любое другое свойство, не работает, по причинам, которые я понимаю, но не знаю, как обойти. Просто у нас есть что-то конкретное, о чем можно поговорить, вот несколько (недействительных) XAML. Я максимально сократил все - изначально были такие атрибуты, как размер дизайнера, но я считаю, что они не имеют отношения к тому, что я пытаюсь сделать.

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

и соответствующий код:

using System.Windows.Controls;

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

    public string MyProperty { get; set; }
  }
}

Сообщение об ошибке:

Ошибка 1 Свойство 'MyProperty' не существует в пространстве имен XML http://schemas.microsoft.com/winfx/2006/xaml/presentation'. Строка 4, Позиция 7.

Теперь я знаю, почему это не удается: элемент имеет тип Page, а Page не имеет свойства с именем MyProperty. Это только объявлено в TestPage ..., который указан атрибутом x:Class, но не самим элементом. Насколько мне известно, эта конфигурация требуется моделью обработки XAML (то есть интеграцией Visual Studio и т. Д.).

Я подозреваю, что я мог бы обработать это с помощью свойства зависимости, но это немного похоже на излишество. Я также мог бы использовать существующее свойство (например, DataContext), а затем скопировать значение в пользовательское свойство в коде позже, но это было бы довольно уродливо.

Выше приведен пример WPF, но я подозреваю, что те же ответы применимы и в Silverlight. Меня интересуют оба - поэтому, если вы разместите ответ, который, как вы знаете, будет работать в одном, а не в другом, я был бы признателен, если бы вы указали это в ответе:)

Я готовлюсь пнуть себя, когда кто-то публикует абсолютно тривиальное решение ...

Ответы [ 9 ]

32 голосов
/ 07 сентября 2010

Вы можете работать с обычным свойством без свойства Dependency, если создаете базовый класс для своей страницы.

 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>

И это работает, хотя MyProperty не является зависимостью или приложением.

8 голосов
/ 07 сентября 2010

Вам нужно сделать его присоединяемым свойством , как заметил Павел, тогда вы можете написать что-то вроде этого

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

Однако, имея только этот код:

Вместо этого вы получите эту ошибку:

Присоединяемое свойство 'MyProperty' не найдено в типе 'SkeetPage'.

Присоединенное свойство 'SkeetPage.MyProperty' не определено для 'Page' или одного из его базовых классов.


Редактировать

К сожалению, вы должны использовать Dependency Properties, это рабочий пример

Страница

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

Код, стоящий за

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

Если вы нажмете кнопку, вы увидите «Тестирование ...» в MessageBox.

3 голосов
/ 07 сентября 2010

Вместо этого вы можете объявить ваш элемент <Page> элементом <TestPage>:

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

Это бы сработало, но вы потеряли InitializeComponent() и стандартные вещи дизайнера.Режим разработки, кажется, все еще работает безупречно, но я не проверял это подробно.

ОБНОВЛЕНИЕ: Это компилирует и запускает, но на самом деле не устанавливает MyProperty.Вы также теряете возможность связывать обработчики событий в XAML (хотя может быть способ восстановить то, что мне неизвестно).

ОБНОВЛЕНИЕ 2: Рабочий пример от @Fredrik Mörk, которыйустанавливает свойство, но не поддерживает обработчики привязки событий в XAML:

Код позади:

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>
2 голосов
/ 07 сентября 2010

Ваш XAML эквивалентен следующему:

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

Это явно незаконно.XAML-файл загружается статическим методом LoadComponent класса Application, и ссылка сообщает:

Загружает XAML-файл, который находится по указанному унифицированному идентификатору ресурса(URI) и преобразует его в экземпляр объекта, указанного корневым элементом файла XAML.

Это означает, что вы можете устанавливать свойства только для типа, указанного корневым элементом.Поэтому вам нужно создать подкласс Page и указать этот подкласс в качестве корневого элемента вашего XAML.

1 голос
/ 12 сентября 2011

Это сработало для меня

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

Так что это может сработать для исходного вопроса (не пробовал).Примечание 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 голос
/ 29 октября 2010

Я просто пытался сделать то же самое с каким-то другим намерением.

Реальный ответ на самом деле таков: вам нужно соглашение WPF о правильных методах Set. Как указано здесь: http://msdn.microsoft.com/en-us/library/ms749011.aspx#custom вам нужно определить методы SetXxx и GetXxx, если вы собираетесь определить присоединенное свойство с именем Xxx.

Итак, посмотрите этот рабочий пример:

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

И часть 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>

Кстати: вам также нужно наследовать DependencyObject

1 голос
/ 07 сентября 2010

Ответ относится к Silverlight.

Нет простого очевидного способа использовать обычное свойство так, как вы хотите, по пути должен быть некоторый компромисс.

На самом деле не работает: -

Некоторые предлагают свойство зависимости.Это не сработает, это все еще публичная собственность от Xaml POV.Присоединенное свойство будет работать, но это сделает работу с ним в коде ужасной.

Закрыть, но без банана: -

Xaml и класс могут быть полностью разделены, какэто: -

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

Код: -

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

}

Однако вы теряете некоторую поддержку от дизайнера.Примечательно, что вам придется создавать поля для хранения ссылок на именованные элементы и назначать их самостоятельно в собственной реализации InitialiseComponent (IMO все эти автоматические поля для именованных элементов в любом случае не всегда полезны).Также дизайнер не будет создавать для вас динамический код события (хотя, как ни странно, он знает, как перейти к тому, который вы создали вручную), однако события, определенные в Xaml, будут подключены во время выполнения.

IMO лучший вариант: -

Лучший компромисс уже был опубликован abhishek, используйте базовый класс shim для хранения свойств.Минимальное усилие, максимальная совместимость.

1 голос
/ 07 сентября 2010

Мое предложение будет DependencyProperty со значением по умолчанию:

    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

Рассматривайте свойства элементов управления как то, что вы открываете для использования во внешнем мире.

Если вы хотите использовать свою собственность, вы можете использовать либо ElementName, либо RelativeSource Bindings.

Насчет чрезмерного убийства, DependencyProperties идут рука об руку с DependencyObjects;)

Дальнейший XAML не требуется, по умолчанию в PropertyMetadata сделает все остальное.

Если вы действительно хотите поместить его в XAML, перейдите к решению для базового класса или, если боги, запретите, добавьте присоединяемое свойство, которое также можно использовать для любого другого элемента управления.

0 голосов
/ 07 сентября 2010

Вам нужно определить это присоединяемое свойство для доступа к нему следующим образом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...