none
Невизуальный компонент RRS feed

  • Вопрос

  • Смысл таков, что есть некий класс, который предоставляет строку. Строка эта может меняться во время работы программы. Строку эту нужно выводить на WPF-контрол Label (и при этом поддерживать её в актуальном состоянии если строка изменится). Так вот, хочу сделать контрол, который будет предоставлять такую строку в виде свойства-зависимости. Т.е. в xaml я создам такой контрол, а в Label, которая должна отображать строку - сделаю привязку к моему невизуальному контролу.
    В общем-то всё это я смог провернуть, но что-бы такая система работала, мне пришлось наследовать мой компонент от System.Windows.FrameworkElement - и мне это не нравится, по причине того, что для такой простой вещи System.Windows.FrameworkElement - слишком "громоздкий" класс (а так же он является визуальным, т.е. мне нужно делать его Visibility = Collapsed). А если наследуюсь от System.Windows.DependencyObject, то никакого "чуда" привязки данных не происходит - при изменении строки в моём контроле, строка в Label не меняется.

    Может ли мне кто-нибудь объяснить почему "система" не работает, если я наследуюсь от System.Windows.DependencyObject?
    То что я "задумал" вообще имеет смысл или привязка данных правомерна только для визуальных контролов?
    16 июля 2012 г. 11:47

Ответы

  • Ваш класс должен реализовывать интерфейс INotifyPropertyChanged

    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    • Помечено в качестве ответа saneea 17 июля 2012 г. 9:21
    16 июля 2012 г. 15:14
    Отвечающий
  • Добрый день.

    Да, можно либо реализовать интерфейс INotifyPropertyChanged или сделать ваш класс потомеом DependencyObject.

    Давайте я вам покажу оба примера.

    Вот так должен выглядеть класс для потомок от INotifyPropertyChanged:

    public class MyClass : INotifyPropertyChanged
    {
        private string _myString;
    
        public string MyString
        {
            get
            {
                return _myString;
            }
            set
            {
                if (value != _myString)
                {
                    _myString = value;
                    OnPropertyChanged("MyString");
                }
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected void OnPropertyChanged(string p_propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(p_propertyName));
            }
        }
    }

    Если у вас есть возможность наследовать от DependencyObject, то лучше конечно от него, в этом случае класс можно описать вот так:

    public class MyClass : DependencyObject
    {
    
        public string MyString
        {
            get { return (string)GetValue(MyStringProperty); }
            set { SetValue(MyStringProperty, value); }
        }
    
        // Using a DependencyProperty as the backing store for MyString.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MyStringProperty =
            DependencyProperty.Register("MyString", typeof(string), typeof(MyClass), new UIPropertyMetadata(""));
    
    }

    Т.к. объявлять ручками DeoendencyProperty достаточно муторно, лучше воспользоваться сниппетом propdp (набираете в том месте где хотите объявить свойство зависимостей propdp и нажимаете кнопку Tab).

    Все, можете для работы следующего кода использовать как первый, так и второй из приведенных классов. Вот разметка формы:

    <Window x:Class="WpfApplication19.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525" >
        <Grid>
            <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="171,48,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
            <Label Content="{Binding MyString}" Height="28" HorizontalAlignment="Left" Margin="10,43,0,0" Name="label1" VerticalAlignment="Top" />
        </Grid>
    </Window>

    Вот код формы:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(MainWindow_Loaded);
        }
    
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            my = new MyClass() { MyString = "Привет" };
            label1.DataContext = my;
        }
    
        MyClass my = null;
    
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            my.MyString = "Пока";
        }
    }

    Все, при запуске увидите в label - "Привет", по нажатию кнопки - "Пока".

    • Помечено в качестве ответа saneea 17 июля 2012 г. 9:21
    17 июля 2012 г. 6:15
    Отвечающий

Все ответы

  • Я WPF не знаю (и не особо понял что там за зависимости и тд),
    но разве нельзя просто сделать событие для строки что-то вроде "OnChange", и по изменению просто записать в текст лабельки?


    • Изменено INFEL8 16 июля 2012 г. 14:12
    16 июля 2012 г. 14:09
  • Можно просто сделать "управление" событием, но это будет менее красиво и менее расширяемо (хотя работать, наверное, будет быстрей). Я описал свою проблему на примере, в действительности всё будет чуть сложней и куча событий типа "OnChange" будет просто запутанной. То что я хочу сделать по сути будет являться сущностями Model (это мой невизуальный компонент) и View (это Label) в патерне Model-View-Controller.
    16 июля 2012 г. 15:02
  • Ваш класс должен реализовывать интерфейс INotifyPropertyChanged

    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    • Помечено в качестве ответа saneea 17 июля 2012 г. 9:21
    16 июля 2012 г. 15:14
    Отвечающий
  • Добрый день.

    Да, можно либо реализовать интерфейс INotifyPropertyChanged или сделать ваш класс потомеом DependencyObject.

    Давайте я вам покажу оба примера.

    Вот так должен выглядеть класс для потомок от INotifyPropertyChanged:

    public class MyClass : INotifyPropertyChanged
    {
        private string _myString;
    
        public string MyString
        {
            get
            {
                return _myString;
            }
            set
            {
                if (value != _myString)
                {
                    _myString = value;
                    OnPropertyChanged("MyString");
                }
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected void OnPropertyChanged(string p_propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(p_propertyName));
            }
        }
    }

    Если у вас есть возможность наследовать от DependencyObject, то лучше конечно от него, в этом случае класс можно описать вот так:

    public class MyClass : DependencyObject
    {
    
        public string MyString
        {
            get { return (string)GetValue(MyStringProperty); }
            set { SetValue(MyStringProperty, value); }
        }
    
        // Using a DependencyProperty as the backing store for MyString.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MyStringProperty =
            DependencyProperty.Register("MyString", typeof(string), typeof(MyClass), new UIPropertyMetadata(""));
    
    }

    Т.к. объявлять ручками DeoendencyProperty достаточно муторно, лучше воспользоваться сниппетом propdp (набираете в том месте где хотите объявить свойство зависимостей propdp и нажимаете кнопку Tab).

    Все, можете для работы следующего кода использовать как первый, так и второй из приведенных классов. Вот разметка формы:

    <Window x:Class="WpfApplication19.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525" >
        <Grid>
            <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="171,48,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
            <Label Content="{Binding MyString}" Height="28" HorizontalAlignment="Left" Margin="10,43,0,0" Name="label1" VerticalAlignment="Top" />
        </Grid>
    </Window>

    Вот код формы:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(MainWindow_Loaded);
        }
    
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            my = new MyClass() { MyString = "Привет" };
            label1.DataContext = my;
        }
    
        MyClass my = null;
    
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            my.MyString = "Пока";
        }
    }

    Все, при запуске увидите в label - "Привет", по нажатию кнопки - "Пока".

    • Помечено в качестве ответа saneea 17 июля 2012 г. 9:21
    17 июля 2012 г. 6:15
    Отвечающий
  • А можно выполнить привязку не через DataContext? Дело в том, что в действительности у меня должно быть несколько "невизуальных компонентов", которые должны биндится один-к-другому... а DataContext - свойство FrameworkElement - т.е. приходим к опять же тому с чего начали...
    20 июля 2012 г. 6:23
  • А можно выполнить привязку не через DataContext? Дело в том, что в действительности у меня должно быть несколько "невизуальных компонентов", которые должны биндится один-к-другому... а DataContext - свойство FrameworkElement - т.е. приходим к опять же тому с чего начали...

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

    Как решить конкретно в вашем случае не могу подсказать, так как не вижу модели вашего приложения.

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


    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    20 июля 2012 г. 6:33
    Отвечающий
  • Да, контролы в одном окне. Да, они имеют имена. Я пытался привязаться через ElementName, но как я уже писал (в изначальной постановке вопроса): 

    ...

     В общем-то всё это я смог провернуть, но что-бы такая система работала, мне пришлось наследовать мой компонент от System.Windows.FrameworkElement - и мне это не нравится, по причине того, что для такой простой вещи System.Windows.FrameworkElement - слишком "громоздкий" класс (а так же он является визуальным, т.е. мне нужно делать его Visibility = Collapsed). А если наследуюсь от System.Windows.DependencyObject, то никакого "чуда" привязки данных не происходит - при изменении строки в моём контроле, строка в Label не меняется.

    ...

    20 июля 2012 г. 6:50
  • Если ваш контрол реализует интерфейс INotifyPropertyChanged или организован так как написал Алексей (до момента объявления в коде окна), то все должно легко работать при обращении через ElementName. Другое дело если вы описываете его не в XAML а создаете где то в коде окна (как во второй части примера Алексея), то тут нужно добавить некое свойство в класс окна, которое будет возвращать этот элемент, что позволит к нему обращаться.

    Вообще контролами принято называть элементы помещаемые в макет окна. Если ваш класс просто реализует некий функционал и не требует помещения в макет, то я бы назвал его диспетчером. Диспетчеры обычно засовываются в общий контекст данных. где реализовано обращение к ним. Контекст данных задается в DataContext всего окна, а в нужных местах идет обращения к частям контекста данных, то есть нужным диспетчерам.

    Но еще раз повторюсь, пока не понятна модель вашего приложения и правильно отвечать сложновато.


    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    20 июля 2012 г. 7:00
    Отвечающий