Форум программистов, компьютерный форум, киберфорум
C#: WPF, UWP и Silverlight
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 5.00/4: Рейтинг темы: голосов - 4, средняя оценка - 5.00
5 / 5 / 0
Регистрация: 12.08.2015
Сообщений: 340
1

DependencyProperty для ItemsControl

23.01.2020, 14:21. Показов 704. Ответов 7
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
В одном из предыдущих постов Элд Хасп посоветовал переделать INPC на DP.

У меня было:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<ItemsControl x:Name="myControl" ItemsSource="{Binding Paths2}" >
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas>
                            <Canvas.RenderTransform>
                                <TransformGroup>
                                    <ScaleTransform ScaleX="{Binding Scale}" ScaleY="{Binding ScaleX, RelativeSource={RelativeSource Self}}"/>
                                    <TranslateTransform X="{Binding PositionX}" Y="{Binding PositionY}" />
                                </TransformGroup>
                            </Canvas.RenderTransform>
                        </Canvas>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.Resources>
        <!--.... (продолжение контрола)-->
И соответственно в CB:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private double _scale = 1;
public double Scale { get => _scale; set { _scale = value; OnPropertyChanged(); } }
double x;
public double PositionX
        {
            get { return x; }
            set { x = value; OnPropertyChanged(); }
        }
double y;
public double PositionY
        {
            get { return y; }
            set { y = value; OnPropertyChanged(); }
        }
Теперь, чтобы реализовать DP, я создаю класс MyControl, в котором определяю свойства (для примера только одно):
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyControl : ItemsControl
    {
        public static readonly DependencyProperty ScaleProperty =
        DependencyProperty.Register("Scale", typeof(double), typeof(MyControl), new PropertyMetadata(1.0, new PropertyChangedCallback(OnScaleChanged)));
 
        private static void OnScaleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // Как установить значение для ScaleX и ScaleY, которые находятся у canvas ?
        }
 
        public double Scale
        {
            get { return (double)GetValue(ScaleProperty); }
            set { SetValue(ScaleProperty, value); }
        }
}
Вопрос простой, что будет находиться в методе OnScaleChanged(). Каким образом я смогу изменить свойство Scale элемента Canvas, который находится в ItemsPanelTemplate.
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
23.01.2020, 14:21
Ответы с готовыми решениями:

Не работает Binding для DependencyProperty
Вот тут не работает привязка: &lt;da:MyShort Value=&quot;{Binding Path=subId}&quot; /&gt; public class...

ItemsControl сортировка
В общем такая тема: Есть ItemsControl вместе с его коллекцией Items, у которого установлено...

Странное поведение ItemsControl
Имеется UserControl, xaml такой: &lt;Grid x:Name=&quot;LayoutRoot&quot; Tap=&quot;LayoutRoot_Tap&quot;&gt; ...

GridSplitter внутри ItemsControl
Никак не могу сделать обыкновенный сплиттер внутри контейнера. XAML такой: &lt;ItemsControl...

7
Модератор
Эксперт .NET
15470 / 10714 / 2788
Регистрация: 21.04.2018
Сообщений: 31,545
Записей в блоге: 2
23.01.2020, 14:52 2
Цитата Сообщение от Semyon001 Посмотреть сообщение
В одном из предыдущих постов Элд Хасп посоветовал переделать INPC на DP.
Я посоветовал
И второе, для привязываемых свойств UI элементов есть специальный тип DependencyProperty. И лучше объявлять DP свойства чем интерфейс INPC. Это относится ко всем типам наследуемым от DependencyObject.
У других типов такие свойства использовать нельзя, поэтому там реализуется INPC и через него сообщается о изменении свойств .
В вашем случае, вы или переделайте все свойства (PositionX, PositionY, Scale) на DP свойства.
Или вынесите всю логику из CB окна в отдельный класс.
Оптимальным в данном случае, будет вынос логики из CB окна в отдельный тип с INPC. Так как нет ни какой необходимости реализовывать весь этот код в CB.
Цитата Сообщение от Semyon001 Посмотреть сообщение
Вопрос простой, что будет находиться в методе OnScaleChanged(). Каким образом я смогу изменить свойство Scale элемента Canvas, который находится в ItemsPanelTemplate.
В public class MyControl : ItemsControl вы задали новые свойства для своего настраиваемого элемента.
После этого в XAML можно их использовать
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<cnt:MyControl x:Name="myControl" ItemsSource="{Binding Paths2}" Scale="{Binding СвойствоVM}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas>
                            <Canvas.RenderTransform>
                                <TransformGroup>
                                    <ScaleTransform ScaleX="{Binding Scale, , RelativeSource={RelativeSource Self}}" 
                                                  ScaleY="{Binding ScaleX, RelativeSource={RelativeSource Self}}"/>
                                    <TranslateTransform X="{Binding PositionX, RelativeSource={RelativeSource Self}}"
                                                        Y="{Binding PositionY, RelativeSource={RelativeSource Self}}" />
                                </TransformGroup>
                            </Canvas.RenderTransform>
                        </Canvas>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.Resources>
        <!--.... (продолжение контрола)-->
RelativeSource={RelativeSource Self} - это ссылка на самого себя

Но ещё раз повторюсь, для данного решения нужно делать не свой настраиваемый элемент с дополнительными свойствами, а вынести логику и дополнительные свойства из CB окна в ViewModel.

Добавлено через 2 минуты
Цитата Сообщение от Элд Хасп Посмотреть сообщение
RelativeSource={RelativeSource Self} - это ссылка на самого себя
Хоты в данном случае надо, наверное, ссылаться на родительский элемент , а не на самого себя.
XML
1
2
3
4
                                    <ScaleTransform ScaleX="{Binding Scale, ElementName=myControl}" 
                                                  ScaleY="{Binding ScaleX, ElementName=myControl}"/>
                                    <TranslateTransform X="{Binding PositionX, ElementName=myControl}"
                                                        Y="{Binding PositionY,ElementName=myControl}" />
0
5 / 5 / 0
Регистрация: 12.08.2015
Сообщений: 340
23.01.2020, 15:08  [ТС] 3
Элд Хасп, мне придется в любом случае определить INPC, либо в VM, либо в CB. То есть наличие/отсутствие DP не влияет на наличие INPC. А если я перенесу это свойство в VM, то я могу привязаться напрямую к свойству ScaleX у Canvas. Зачем тогда нужна будет лишняя прослойка в виде DP Scale?

Добавлено через 38 секунд
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Хоты в данном случае надо, наверное, ссылаться на родительский элемент , а не на самого себя.
Так вернее

Добавлено через 2 минуты
Или в CB для изменения DP свойства Scale, мы должны обращаться к контролу по имени и изменять Scale,в этом случае INPC не нужен?
0
Модератор
Эксперт .NET
15470 / 10714 / 2788
Регистрация: 21.04.2018
Сообщений: 31,545
Записей в блоге: 2
23.01.2020, 16:38 4
Цитата Сообщение от Semyon001 Посмотреть сообщение
То есть наличие/отсутствие DP не влияет на наличие INPC
Влияет.
Если вы делаете UI элемент или любой другой производный от DependencyObject тип, то в нём вы вместо обычных свойств объявляете DP свойства. И INPC там не нужен.
Окно (Window) - это тоже производный от DependencyObject класс. Поэтому в нём нет необходимости объявлять INPC.

В вашей же задаче, вернее в реализации её решения, вам понадобился INPC, в первую очередь, из-за того, что дополнительный свойства и их логика у вас расположены в CB окна, а не в отдельном типе.

Если вам понадобился INPC в производном от DependencyObject классе, то скорее всего вы что-то делаете не верно.

Добавлено через 8 минут
Цитата Сообщение от Semyon001 Посмотреть сообщение
в этом случае INPC не нужен?
INPC нужен для извещения привязок (Binding) об изменении значения свойства.
DP свойства и так уже имеют механизм для этого.
А для обычных свойств нужен INPC.
0
Модератор
Эксперт .NET
15470 / 10714 / 2788
Регистрация: 21.04.2018
Сообщений: 31,545
Записей в блоге: 2
23.01.2020, 17:51 5
Semyon001, вот вам действующий пример варианта реализации вашей задачи.

Создаёте тип для данных. Так как в данном случае это тип для View, то в отдельной Модели нет смысла.
Сеттеры у свойств приватны. Для присвоения значений свойствам сделаны отдельные методы.
Это сделано специально с цель облегчения понимания работы INPC. В реале так делать необязательно.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
    public class PathsViewModel : OnPropertyChangedClass
    {
        public Point Offset { get; private set; }
        public void SetOffset(Point offset)
        {
            Offset = offset;
            OnPropertyChanged(nameof(Offset));
            SetOffsetCurrent();
        }
        public Point OffsetCurrent { get; private set; }
        public void SetOffsetCurrent()
        {
            OffsetCurrent = Offset;
            OnPropertyChanged(nameof(OffsetCurrent));
        }
        public void SetOffsetCurrent(Point offsetCurrent)
        {
            OffsetCurrent = new Point(Offset.X + offsetCurrent.X, Offset.Y + offsetCurrent.Y);
            OnPropertyChanged(nameof(OffsetCurrent));
        }
        public void SetOffsetCurrent(Point offsetBegin, Point offsetEnd)
        {
            OffsetCurrent = new Point
                (
                    Offset.X - offsetBegin.X + offsetEnd.X,
                    Offset.Y - offsetBegin.Y + offsetEnd.Y
                );
            OnPropertyChanged(nameof(OffsetCurrent));
        }
 
        public double Scale { get; private set; } = 1.0;
        public void ChangeScale(int direction)
        {
            if (direction > 0)
                Scale *= 1.1;
            else if (direction < 0)
                Scale *= 0.9;
            else
                return;
            OnPropertyChanged(nameof(Scale));
        }
        public CompositeCollection Paths { get; } = new CompositeCollection();
        public PathsViewModel() { }
        public PathsViewModel(params object[] geometries)
            => AddGeometries(geometries);
 
        public void AddGeometries(params object[] geometries)
        {
            foreach (Geometry geometry in geometries)
                Paths.Add(geometry);
        }
    }
Подключаете его в XAML
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<Window x:Class="WpfApp19.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp19" mc:Ignorable="d"
        MouseLeftButtonUp="ItemsControl_MouseLeftButtonUp" MouseLeave="Window_MouseLeave"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:PathsViewModel>
            <local:PathsViewModel.Paths>
                <Point X="100" Y="100"/>
                <RectangleGeometry>
                    <RectangleGeometry.Rect>
                        <Rect X="100" Y="100" Width="100" Height="100"/>
                    </RectangleGeometry.Rect>
                </RectangleGeometry>
            </local:PathsViewModel.Paths>
        </local:PathsViewModel>
    </Window.DataContext>
    <Grid ShowGridLines="True">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ItemsControl ItemsSource="{Binding Paths}"  BorderBrush="Green" Background="WhiteSmoke" BorderThickness="2"
                            PreviewMouseWheel="ItemsControl_PreviewMouseWheel"
                            MouseLeftButtonDown="ItemsControl_MouseLeftButtonDown" 
                            MouseMove="ItemsControl_MouseMove" 
                            ClipToBounds="True">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas>
                        <Canvas.LayoutTransform>
                            <ScaleTransform ScaleX="{Binding Scale}" ScaleY="{Binding ScaleX, RelativeSource={RelativeSource Self}}"/>
                        </Canvas.LayoutTransform>
                        <Canvas.RenderTransform>
                            <TranslateTransform X="{Binding OffsetCurrent.X}" Y="{Binding OffsetCurrent.Y}" />
                        </Canvas.RenderTransform>
                    </Canvas>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.Resources>
                <DataTemplate DataType="{x:Type RectangleGeometry}">
                    <Path Data="{Binding}" Fill="AliceBlue" Stroke="Red" StrokeThickness="2"/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type Point}">
                    <Path Stroke="Black" Fill="LightBlue">
                        <Path.Data>
                            <EllipseGeometry Center="{Binding}" RadiusX="6" RadiusY="6" />
                        </Path.Data>
                    </Path>
                </DataTemplate>
            </ItemsControl.Resources>
        </ItemsControl>
    </Grid>
</Window>
В CB задаёте обработку ВИЗУАЛЬНЫХ событий и передачу данных в ViewModel
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    public partial class MainWindow : Window
    {
        public MainWindow() => InitializeComponent();
 
        private void ItemsControl_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (!(DataContext is PathsViewModel viewModel))
                return;
 
            viewModel.ChangeScale(-e.Delta);
        }
 
        bool isMove = false;
        Point mousePoint = new Point();
 
        private void ItemsControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!(DataContext is PathsViewModel))
                return;
 
            isMove = true;
            mousePoint = e.GetPosition(null);
        }
 
        private void ItemsControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (!(DataContext is PathsViewModel viewModel) || !isMove)
                return;
 
            isMove = false;
            viewModel.SetOffset(viewModel.OffsetCurrent);
        }
 
        private void Window_MouseLeave(object sender, MouseEventArgs e)
        {
            if (!(DataContext is PathsViewModel viewModel) || !isMove)
                return;
 
            isMove = false;
            viewModel.SetOffset(viewModel.OffsetCurrent);
 
        }
 
        private void ItemsControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (!(DataContext is PathsViewModel viewModel) || !isMove)
                return;
 
                viewModel.SetOffsetCurrent(mousePoint, e.GetPosition(null));
        }
    }
На всякий случай архив с Решением прилагаю.
Вложения
Тип файла: 7z Semyon001-2.7z (25.2 Кб, 1 просмотров)
1
5 / 5 / 0
Регистрация: 12.08.2015
Сообщений: 340
24.01.2020, 11:12  [ТС] 6
Элд Хасп, а чем отличается ваш класс OnPropertyChangedClass от стандартной реализации интерфейса INotifyPropertyChanged:
C#
1
2
3
4
5
public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName]string prop = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
        }
С точки зрения наследования лучше оставить возможность унаследоваться от какого-либо другого класса, ибо множественного наследования в С# нет.
0
управление сложностью
1687 / 1300 / 259
Регистрация: 22.03.2015
Сообщений: 7,545
Записей в блоге: 5
24.01.2020, 11:25 7
Цитата Сообщение от Semyon001 Посмотреть сообщение
ибо множественного наследования в С# нет.
не забывайте про интерфейсы
0
Модератор
Эксперт .NET
15470 / 10714 / 2788
Регистрация: 21.04.2018
Сообщений: 31,545
Записей в блоге: 2
24.01.2020, 15:11 8
Цитата Сообщение от Semyon001 Посмотреть сообщение
отличается ваш класс OnPropertyChangedClass от стандартной реализации интерфейса INotifyPropertyChanged:
Тем, что не надо делать каждый раз реализацию по новой.

И посмотрите сам класс.
В данном случае используется только метод OnPropertyChanged. Это сделано с демонстрационной целью.

В реальных приложениях свойства объявляются так
C#
1
2
3
        private Point _offset;
        public Point Offset { get => _offset; private set => SetProperty(ref _offset, value); }
        public void SetOffset(Point offset) => Offset = offset;
А отслеживание значений свойств делается отдельно в переопределяемом методе PropertyNewValue.

В целом в реальном приложении класс выглядел бы примерно так

Класс со свойствами
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    /// <summary>Класс в котором только объявляются свойства 
    /// и методы для присвоения им значений</summary>
    public class _PathsViewModel : OnPropertyChangedClass
    {
        private Point _offset;
        public Point Offset
        {
            get => _offset;
#if !DEBUG
            private
#endif
            set => SetProperty(ref _offset, value);
        }
        public void SetOffset(Point offset) => Offset = offset;
 
        private Point _offsetCurrent;
        public Point OffsetCurrent
        {
            get => _offsetCurrent;
#if !DEBUG
            private
#endif
            set => SetProperty(ref _offsetCurrent, value);
        }
        public void SetOffsetCurrent()
            => OffsetCurrent = Offset;
 
        public void SetOffsetCurrent(Point offsetCurrent)
            => OffsetCurrent = new Point(Offset.X + offsetCurrent.X, Offset.Y + offsetCurrent.Y);
 
        public void SetOffsetCurrent(Point offsetBegin, Point offsetEnd)
            => OffsetCurrent = new Point
                (
                    Offset.X - offsetBegin.X + offsetEnd.X,
                    Offset.Y - offsetBegin.Y + offsetEnd.Y
                );
 
        private double _scale = 1.0;
        public double Scale
        {
            get => _scale;
#if !DEBUG
            private
#endif
            set => SetProperty(ref _scale, value);
        }
        public void ChangeScale(int direction)
            => Scale *= direction > 0 ? 1.1 : direction < 0 ? 0.9 : 1;
 
        public CompositeCollection Paths { get; } = new CompositeCollection();
 
        public void AddGeometries(params object[] geometries)
        {
            foreach (Geometry geometry in geometries)
                Paths.Add(geometry);
        }
 
    }
Класс с логикой для View
C#
1
2
3
4
5
6
7
8
9
10
11
12
    public class PathsViewModel : _PathsViewModel
    {
        public PathsViewModel() { }
        public PathsViewModel(params object[] geometries)
            => AddGeometries(geometries);
        protected override void PropertyNewValue<T>(ref T fieldProperty, T newValue, string propertyName)
        {
            base.PropertyNewValue(ref fieldProperty, newValue, propertyName);
            if (propertyName == nameof(Offset))
                OffsetCurrent = Offset;
        }
    }
Здесь нет ещё Модели. А так был бы ещё один производный класс с методами для работы с Моделью.

В Конструторе XAML мы получаем возможность проверять как будут меняться элементы при изменении значений в ViewModel
XML
1
        <local:PathsViewModel Offset="50,50" Scale=".5" OffsetCurrent="20,20">
Такое разделение объявления свойств, логики View, логики Model позволяет значительно уменьшить вероятность случайных ошибок, облегчает конструирование View и её отладку. Что в итоге ускоряет реализацию приложения в целом. Особенно в случаях раздельно разработки Model, View, ViewModel - что типично для WPF решений.
0
24.01.2020, 15:11
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
24.01.2020, 15:11
Помогаю со студенческими работами здесь

Полосы прокрутки у ItemsControl
Пытаюсь сделать полосы прокрутки у ItemsControl. Помещаю его внутрь ScrollViewer, но получается,...

В ItemsControl добавить LayoutTransform
У меня есть контрол, в котором отображаются два контрола друг на друге , но факту это две картинки...

ItemsControl внутри TabControl
Наткнулся на очередное непонятное поведение. Есть главная форма с 'TabControl': ...

Условный вывод Item'a в ItemsControl
Добрый день. В окне есть ItemsControl с забинденной к нему коллекцией объектов некоторого класса X....


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
8
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru