powered by simpleCommunicator - 2.0.34     © 2025 Programmizd 02
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / Освоение Attached Properties: Списочное свойство
18 сообщений из 18, страница 1 из 1
Освоение Attached Properties: Списочное свойство
    #39948621
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Пытаюсь освоить для себе новую тему: Attached Properties.

Пытаюсь сделать списочное свойство.
Реализовал так
Код: 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.
	public class ExampleAP : DependencyObject
	{
		public static IEnumerable GetItems(DependencyObject obj)
		{
			return (IEnumerable)obj.GetValue(ItemsProperty);
		}

		public static void SetItems(DependencyObject obj, List<object> value)
		{
			obj.SetValue(ItemsProperty, value);
		}

		// Using a DependencyProperty as the backing store for Items.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty ItemsProperty =
			DependencyProperty.RegisterAttached("Items", typeof(IEnumerable), typeof(ExampleAP),
                              new PropertyMetadata(new List<object>(), ItemsChanged));

		private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			if (d is Panel panel)
				foreach (object item in (IEnumerable)e.NewValue)
					panel.Children.Add(new TextBlock() {Text = item.ToString() });
		}
	}



Использование
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
        <StackPanel Height="400" Width="400">
            <local:ExampleAP.Items>
                <collections:ArrayList>
                    <sys:String>Привет</sys:String>
                    <sys:String>Пока</sys:String>
                </collections:ArrayList>
            </local:ExampleAP.Items>
        </StackPanel>



Но хотелось бы избавиться от <collections:ArrayList>
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
        <StackPanel Height="400" Width="400">
            <local:ExampleAP.Items>
                <!--<collections:ArrayList>-->
                    <sys:String>Привет</sys:String>
                    <sys:String>Пока</sys:String>
                <!--</collections:ArrayList>-->
            </local:ExampleAP.Items>
        </StackPanel>



Попробовал так, но не вышло
Код: 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.
	public class ExampleAP : DependencyObject
	{
		public static List<object> GetItems(DependencyObject obj)
		{
			return (List<object>)obj.GetValue(ItemsProperty);
		}

		public static void SetItems(DependencyObject obj, List<object> value)
		{
			obj.SetValue(ItemsProperty, value);
		}

		// Using a DependencyProperty as the backing store for Items.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty ItemsProperty =
			DependencyProperty.RegisterAttached("Items", typeof(List<object>), typeof(ExampleAP),
                              new PropertyMetadata(new List<object>(), ItemsChanged));

		private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			if (d is Panel panel)
				foreach (object item in (List<object>)e.NewValue)
					panel.Children.Add(new TextBlock() {Text = item.ToString() });
		}
	}




Пытался сделать по аналогии с Behaviors - тоже не вышло.
Код: 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.
	public class ItemCollection : IEnumerable<object>
	{
		public IEnumerator<object> GetEnumerator()
			=> Items.GetEnumerator();

		IEnumerator IEnumerable.GetEnumerator()
			=> Items.GetEnumerator();

		protected readonly List<object> items = new List<object>();
		public ReadOnlyCollection<object> Items { get; }

		public Panel Panel { get; }

		public ItemCollection(Panel panel)
		{
			if (panel == null)
				throw new ArgumentNullException(nameof(panel));

			Panel = panel;
			Items = items.AsReadOnly();
		}

		public void Add(object item)
		{
			items.Add(item);
			Panel.Children.Add(new TextBlock() { Text = Panel.ToString() + ": " + item.ToString() });
		}

		public void AddRange(IEnumerable enumerable)
		{
			foreach (object item in enumerable)
				Add(item);
		}
	}



Код: 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.
	public class ExampleAP : DependencyObject
	{

		public static ItemCollection GetItems(DependencyObject obj)
		{
			if (!(obj is Panel panel))
				throw new ArgumentException("This is not a Panel", nameof(obj));

			ItemCollection collection = (ItemCollection)obj.GetValue(ItemsProperty);
			if (collection == null)
			{
				collection = new ItemCollection(panel);
				panel.SetValue(ItemsProperty, collection);
			}
			return collection;
		}

		public static void SetItems(DependencyObject obj, ItemCollection value)
		{
			if (!(obj is Panel panel))
				throw new ArgumentException("This is not a Panel", nameof(obj));
			obj.SetValue(ItemsProperty, value);
		}

		// Using a DependencyProperty as the backing store for Items.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty ItemsProperty =
			DependencyProperty.RegisterAttached("Items", typeof(ItemCollection), typeof(ExampleAP), new PropertyMetadata(null, ItemsChanged));

		private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			if (d is Panel panel)
				GetItems(panel).AddRange((IEnumerable)e.NewValue);
		}

	}



Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
    <Grid>
        <StackPanel Height="400" Width="400">
            <local:ExampleAP.Items>
                <!--<collections:ArrayList>-->
                    <sys:String>Привет</sys:String>
                    <sys:String>Пока</sys:String>
                <!--</collections:ArrayList>-->
            </local:ExampleAP.Items>
            <WrapPanel Margin="0,40">
                <local:ExampleAP.Items>
                    <!--<collections:ArrayList>-->
                    <sys:String>Привет Wrap</sys:String>
                    <sys:String>Пока Wrap</sys:String>
                    <!--</collections:ArrayList>-->
                </local:ExampleAP.Items>
            </WrapPanel>
        </StackPanel>
    </Grid>



Какие есть мысли на этот счёт?
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39948739
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
фигня какая то, если честно, они не для этого предназначены
Код: c#
1.
new PropertyMetadata(new List<object>(), ItemsChanged));



вот тут ошибка, вы в статике объявляете экземпляр объекта List<object>, но получается, что все экземпляры объектов будут иметь общий список, ведь инстанцирование общего произошло в статике, правильно написать Null и если нужно сделать список по умолчанию инициализировать его из конструктора экземпляра, а не статического.
Но упс, а как это сделать с AttachedProperty? конструктор то не доступен. По этому избавляться от ArrayList не стоит, есть еще CompositeCollection, возможно имеет смысл пользоваться ей.
Для генерации элементов управления на панель используют механизм похожий на ItemsControl, в 99% этот элемент управления и его производные позволяют делать логику с большим количеством элементов, на произвольной панели, ведь представление независимо и мы можем изменить его в любое другое, вопрос полёта фантазии.

Панель сама по себе содержит коллекцию объектов и можно было просто написать
Код: xml
1.
2.
3.
4.
        <StackPanel Height="400" Width="400">
            <ContentPresenter Content="Привет" />
            <ContentPresenter Content="пока" />
        </StackPanel>
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39948763
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Roman Mejtes
фигня какая то, если честно, они не для этого предназначены

Разве Interaction.Behaviors не так работает?

Roman Mejtes
Код: c#
1.
new PropertyMetadata(new List<object>(), ItemsChanged));



вот тут ошибка, вы в статике объявляете экземпляр объекта List<object>, но получается, что все экземпляры объектов будут иметь общий список, ведь инстанцирование общего произошло в статике, правильно написать Null и если нужно сделать список по умолчанию инициализировать его из конструктора экземпляра, а не статического.

Да, я это знаю.
И привёл примеры для показа, что таким образом XAML конструктор ошибки не выдаёт.
Но как вы правили отметили, логика работы будет совсем не та что требуется.

Загвоздка именно в том как инициализировать коллекцию передав ей к какому элементу она присоединена.

Третий вариант сделан по подобию Interaction.Behaviors https://github.com/microsoft/XamlBehaviorsWpf/blob/master/src/Microsoft.Xaml.Behaviors/Interaction.cs
Там по логике при первом обращении идёт инициализация коллекции.
В Interaction.Behaviors это обращение идёт через оболочку GetBehaviors поэтому получается передать в коллекцию ссылку на родительский элемент.
Я, на мой взгляд, сделал точную копию этого механизма.
Но с моим кодом при исполнении XAML кода нет обращения к GetItems .
XAML сразу обращается к GetValue(ItemsProperty) .
По этой причине, не происходит инициализации коллекции и вылетает исключение System.Windows.Markup.XamlParseException : ""Свойство коллекции "System.Windows.Controls.StackPanel"."Items" не определено (null).": номер строки "21" и позиция в строке "18"."

Roman Mejtes

Для генерации элементов управления на панель используют механизм похожий на ItemsControl, в 99% этот элемент управления и его производные позволяют делать логику с большим количеством элементов, на произвольной панели, ведь представление независимо и мы можем изменить его в любое другое, вопрос полёта фантазии.


Знаю и использую постоянно.
В данном случае сделан простейший демо-пример, чтобы уменьшить количество деталей не относящихся к сути вопроса.

Чтобы внести ясность, привожу пример сделанный по подобию Interaction.Behaviors https://github.com/microsoft/XamlBehaviorsWpf/blob/master/src/Microsoft.Xaml.Behaviors/Interaction.cs
Сам пакет я не смог локально скомпилировать у себя - не хватает каких-то ссылок. Так и не разобрался откуда их взять.

Реализацию у себя повторил. Здесь привожу, код только относящийся к вопросу.

Коллекция для использования в свойстве.
Если при инициализации передать Panel , то логика работает правильно
Код: 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.
	public class ItemsCollection : ObservableCollection<object>
	{
		/// <summary>For use in XAML</summary>
		public ItemsCollection() { }

		/// <summary>The panel to which the attached property belongs</summary>
		public Panel Panel { get; private set; }

		/// <summary>Constructor with Panel Definition</summary>
		/// <param name="panel">The panel to which the attached property belongs</param>
		public ItemsCollection(Panel panel)
		{
			if (panel == null)
				throw new ArgumentNullException(nameof(panel));

			Panel = panel;
		}

		protected override void InsertItem(int index, object item)
		{
			base.InsertItem(index, item);
			Panel?.Children.Add(new TextBlock() { Text = Panel.GetType().Name + ": " + item.ToString() });
		}
	}




Само свойство.
Если будет обращение к GetItems(Panel panel) , то всё будет работать нормально.
Код: 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.
	public class ExampleAP : DependencyObject
	{
		public static ItemsCollection GetItems(Panel panel)
		{
			if (panel == null)
				throw new ArgumentNullException(nameof(panel));

			ItemsCollection collection = (ItemsCollection)panel.GetValue(ItemsProperty);
			if (collection == null)
			{
				collection = new ItemsCollection(panel);
				panel.SetValue(ItemsProperty, collection);
			}
			return collection;
		}

		public static void SetItems(Panel panel, ItemsCollection value)
		{
			if (panel == null)
				throw new ArgumentNullException(nameof(panel));
			panel.SetValue(ItemsProperty, value);
		}

		// Using a DependencyProperty as the backing store for Items.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty ItemsProperty =
			DependencyProperty.RegisterAttached("Items", typeof(ItemsCollection), typeof(ExampleAP),
				new FrameworkPropertyMetadata(null, ItemsChanged));

		private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			if (d is Panel panel)
			{
				ItemsCollection collection = GetItems(panel);
				if (collection != e.NewValue)
				{
					foreach (object item in (IEnumerable)e.NewValue)
						collection.Add(item);
				}

			}
		}
	}



Если в XAML задать коллекцию, то окно запускается, но будет пустым.
Так как коллекция "не знает" к какой панели она присоединена.
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
    <Grid>
        <StackPanel x:Name="canvas" Height="400" Width="400">
            <local:ExampleAP.Items>
                <local:ItemsCollection>
                    <sys:String>Привет</sys:String>
                    <sys:String>Пока</sys:String>
                </local:ItemsCollection>
            </local:ExampleAP.Items>
            <WrapPanel x:Name="wrap" Margin="40,0">
                <local:ExampleAP.Items>
                    <local:ItemsCollection>
                        <sys:String>Привет Wrap</sys:String>
                        <sys:String>Пока Wrap</sys:String>
                    </local:ItemsCollection>
                </local:ExampleAP.Items>
            </WrapPanel>
        </StackPanel>
    </Grid>
</Window>



Если панели в XAML оставить пустыми и задавать свойство в CB, то всё работает правильно.
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
	public partial class MainWindow : Window
	{
		public MainWindow() => InitializeComponent();

		private void window_Loaded(object sender, RoutedEventArgs e)
		{
			ExampleAP.GetItems(canvas).Add("Привет CB");
			ExampleAP.GetItems(canvas).Add("Пока CB");
			ExampleAP.GetItems(wrap).Add("Привет Wrap CB");
			ExampleAP.GetItems(wrap).Add("Пока Wrap CB");
		}
	}



Если же повторить это в XAML (так же как с Interaction.Behaviors ), то при запуске на исполнение поймаем ошибку.
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
    <Grid>
        <StackPanel x:Name="canvas" Height="400" Width="400">
            <local:ExampleAP.Items>
                <sys:String>Привет</sys:String>
                <sys:String>Пока</sys:String>
            </local:ExampleAP.Items>
            <WrapPanel x:Name="wrap" Margin="40,0">
                <local:ExampleAP.Items>
                    <sys:String>Привет Wrap</sys:String>
                    <sys:String>Пока Wrap</sys:String>
                </local:ExampleAP.Items>
            </WrapPanel>
        </StackPanel>
    </Grid>
</Window>



Есть какой-то нюанс в реализации Interaction.Behaviors который я не могу уловить.
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39948841
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Реализация в самом деле странная. Классы с объявленными AttachedProperty обычно ни от чего (в.т.ч. DependencyObject) не наследуют, эти классы вообще объявляют как static. Далее, пользователю желательно не давать доступа к созданию коллекции, у него должна быть возможность только объявлять элементы коллекции. В итоге получается как-то так:
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
public static class AttachedList
{
  static readonly DependencyPropertyKey listPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
    "List", typeof(IList), typeof(AttachedList),
    new PropertyMetadata(new List<string>())
  );

  public static readonly DependencyProperty ListProperty = listPropertyKey.DependencyProperty;

  public static IList GetList(DependencyObject o)
  {
    var list = (IList) o.GetValue(ListProperty);
    if (list != null) return list;
    list = new List<string>();
    o.SetValue(listPropertyKey, list);
    return list;
  }
}


и в разметке так:
Код: xml
1.
2.
3.
4.
5.
6.
7.
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=(local:AttachedList.List), Mode=OneWay}">
  <local:AttachedList.List>
    <system:String>Foo</system:String>
    <system:String>Bar</system:String>
    <system:String>Zot</system:String>
  </local:AttachedList.List>
</ListBox>
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39948980
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Roman Mejtes и Сон Веры Павловны , спасибо за вниание!
После ваших ответов сравнил более внимательнее свою реализацию с реализацией Behaviors.
Как оказалось, в Behaviors используют нестандартную реализацию Attached Properties.
У них различается названия свойств в оболочке сеттера и в регистрации свойства.

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
		private static readonly DependencyProperty BehaviorsProperty
			= DependencyProperty.RegisterAttached
			(
				"ShadowBehaviors", // Регистрируется ShadowBehaviors
				typeof(BehaviorCollection),
				typeof(Interaction),
				new FrameworkPropertyMetadata(
				new PropertyChangedCallback(OnBehaviorsChanged))
			);

		public static BehaviorCollection GetBehaviors(DependencyObject obj) // А в здесь в оболочке другое название
		{
			BehaviorCollection behaviorCollection = (BehaviorCollection)obj.GetValue(Interaction.BehaviorsProperty);
			if (behaviorCollection == null)
			{
				behaviorCollection = new BehaviorCollection();
				obj.SetValue(Interaction.BehaviorsProperty, behaviorCollection);
			}
			return behaviorCollection;
		}



Подправил свою реализацию и получи то, что хотел
Код: 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.
	public class ItemsCollection : ObservableCollection<object>
	{
		/// <summary>For use in XAML</summary>
		public ItemsCollection() { }

		/// <summary>The panel to which the attached property belongs</summary>
		public Panel Panel { get; private set; }

		/// <summary>Constructor with Panel Definition</summary>
		/// <param name="panel">The panel to which the attached property belongs</param>
		public ItemsCollection(Panel panel)
		{
			if (panel == null)
				throw new ArgumentNullException(nameof(panel));

			Panel = panel;
		}

		protected override void InsertItem(int index, object item)
		{
			base.InsertItem(index, item);
			Panel?.Children.Add(new TextBlock() { Text = Panel.GetType().Name + ": " + item.ToString() });
		}

		/// <summary>Panel Definition</summary>
		/// <param name="panel">The panel to which the attached property belongs</param>
		public void SetPanel(Panel panel)
		{
			if (Panel == panel)
				return;

			if (Panel != null)
				throw new Exception("Panel is already installed");

			if (panel == null)
				throw new ArgumentNullException(nameof(panel));
			Panel = panel;

			foreach (object item in this)
				Panel.Children.Add(new TextBlock() { Text = Panel.GetType().Name + ": " + item.ToString() });
		}

	}


Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
	public static class ExampleAP /*: DependencyObject*/
	{
		public static ItemsCollection GetItems(Panel panel)
		{
			if (panel == null)
				throw new ArgumentNullException(nameof(panel));

			ItemsCollection collection = (ItemsCollection)panel.GetValue(ItemsProperty);
			if (collection == null)
			{
				collection = new ItemsCollection(panel);
				panel.SetValue(ItemsProperty, collection);
			}
			collection.SetPanel(panel);
			return collection;
		}

		// Using a DependencyProperty as the backing store for Items.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty ItemsProperty =
			DependencyProperty.RegisterAttached("ShadowItems", typeof(ItemsCollection), typeof(ExampleAP),
				new FrameworkPropertyMetadata(null));

	}


Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
    <Grid>
        <StackPanel x:Name="canvas" Height="400" Width="400">
            <local:ExampleAP.Items>
                <sys:String>Привет</sys:String>
                <sys:String>Пока</sys:String>
            </local:ExampleAP.Items>
            <WrapPanel x:Name="wrap" Margin="40,0" Orientation="Vertical">
                <local:ExampleAP.Items>
                    <sys:String>Привет Wrap</sys:String>
                    <sys:String>Пока Wrap</sys:String>
                </local:ExampleAP.Items>
            </WrapPanel>
        </StackPanel>
    </Grid>
</Window>



То что регистрируемое имя может отличаться от имени свойства - даже в голову не приходило.
Теперь заинтересовал сам механизм регистрации свойств и взаимодействи XAML с ними.

Roman Mejtes и Сон Веры Павловны , если знаете, то подскажите где об этом можно глубже и подробнее прочитать.
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39949042
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Eld Hasp
Как оказалось, в Behaviors используют нестандартную реализацию Attached Properties.
У них различается названия свойств в оболочке сеттера и в регистрации свойства.

Нет здесь ничего особенно нестандартного, методы GetProperty/SetProperty - это просто наименования аксессоров для доступа к DP из кода или XAML-разметки, и да, имена этих аксессоров в части указания на имя свойства могут быть любыми. Просто обычно принято, что имя свойства совпадает с именем аксессора, но если это нарушить, катастрофы не случится. Разве что от благодарных коллег можно получить по голове за такие ребуcы.
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39949068
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Сон Веры Павловны
Нет здесь ничего особенно нестандартного, методы GetProperty/SetProperty - это просто наименования аксессоров для доступа к DP из кода или XAML-разметки, и да, имена этих аксессоров в части указания на имя свойства могут быть любыми. Просто обычно принято, что имя свойства совпадает с именем аксессора, но если это нарушить, катастрофы не случится. Разве что от благодарных коллег можно получить по голове за такие ребуcы.

Я думал, для WPF - это соблюдать обязательно.
Получается если есть такое зарегистрированное свойство, то код XAML обращается напрямую к нему.
Если нет, то через Set/Get методы оболочки.

Надо поэкспериментировать с таким необычным объявлением - как будут вести себя привязки.
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39949138
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
за сохранение, чтения или сброс значения свойства зависимости отвечает не свойство зависимости, а объект зависимости. А свойство зависимости является просто ключём, к этому свойству. Как сказал выше Сон Веры Павловны, это просто методы
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39949253
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Roman Mejtes
за сохранение, чтения или сброс значения свойства зависимости отвечает не свойство зависимости, а объект зависимости. А свойство зависимости является просто ключём, к этому свойству. Как сказал выше Сон Веры Павловны, это просто методы

Можно сделать вывод, что при компиляции кода XAML при обращении к присоединённому свойству есть некая логика по проверке регистрации этого свойства.
Если его имя зарегистрировано, то идёт обращение напрямую к Attached Propertу через obj.SetValue(...) и obj.GetValue(...) .
Если не зарегистрировано, то к методам оболочки SetСвойство(obj) и GetСвойство(obj) .

Для меня такая логика оказалось неожиданностью.
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39949292
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Eld Hasp
Можно сделать вывод, что при компиляции кода XAML при обращении к присоединённому свойству есть некая логика по проверке регистрации этого свойства.
Если его имя зарегистрировано, то идёт обращение напрямую к Attached Propertу через obj.SetValue(...) и obj.GetValue(...) .
Если не зарегистрировано, то к методам оболочки SetСвойство(obj) и GetСвойство(obj) .

Для меня такая логика оказалось неожиданностью.

Опять не так. Выше подразумевалось, что для манипуляции со значениями DP объекту просто достаточно иметь само свойство, и получать/менять значения через вызовы obj.GetValue/obj.SetValue. Для внешних интерфейсов к таким вызовам делаются методы доступа, но их может и не быть (т.е. наличие методов GetProperty/SetProperty необязательно), или их имя может отличаться от имени самого свойства.
А имя свойства всегда и в любом случае должно быть зарегистрировано.
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39949327
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Сон Веры Павловны , я за поведение XAML, если имя свойства и зарегистрированное имя различаются.
В Interop от Microsoft имя свойства "Behaviors", а регистрируется "ShadowBehaviors".
В своём коде, для получения такого же поведения, мне пришлось сделать так же.

Получается, что XAML код по разному работает для случаев с одинаковым и разными именами свойства и регистрируемого.

В случае если имена одинаковые он использует obj.Set/GetValue(СвойствоProperty,...).

В случае если имена различны, я думал, что будет ошибка.
А оказалось, что XAML просто переходит на использование методов оболочки Set/GetСвойство(obj,...).

Вот и получается в итоге - чтобы заставить XAML работать всегда через методы оболочки, то надо ОБЯЗАТЕЛЬНО регистрировать имя отличающееся от имени свойства.

Конечно, этих "танцев с бубном" можно было бы избежать, если бы был обратный вызов при инициализации свойства, по аналогии с обратным вызовом при его изменении. Но такого или нет...ну, или я не знаю о такой возможности.
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39949408
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Eld Hasp
Вот и получается в итоге - чтобы заставить XAML работать всегда через методы оболочки, то надо ОБЯЗАТЕЛЬНО регистрировать имя отличающееся от имени свойства.

А, теперь понял, о чем речь. Вот тут товарищ об этом писал: https://docs.microsoft.com/en-us/archive/blogs/johngossman/how-to-initialize-an-attached-dependencyproperty-of-type-collection
The normal way to do this is to initialize the property in the CLR getter, but attached properties were skipping calling the getter. This is an optimization: the XAML parser accesses the DP directly rather than use reflection to find and call the CLR property. Well, the fix is easy: hide the DP from the XAML parser, which can be done by using a different name for the registered DP (and making it internal is a good idea), and defining the attached property by using the static getter and setter.
Но здесь тоже есть один нюанс. Модифицируем мой вышеприведенный пример вот так:
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
public static class AttachedList
{
  static readonly DependencyPropertyKey itemsPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
    "List", typeof(IList), typeof(AttachedList),
    new PropertyMetadata(null)
  );

  public static readonly DependencyProperty ItemsProperty = itemsPropertyKey.DependencyProperty;

  public static IList GetItems(DependencyObject o)
  {
    var list = (IList) o.GetValue(ItemsProperty);
    if (list != null) return list;
    list = new List<string>();
    o.SetValue(itemsPropertyKey, list);
    return list;
  }
}


Тогда разметка должна выглядеть именно так:
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
<ListBox
  ItemsSource="{Binding RelativeSource={RelativeSource Self},
  Path=(wpftest2:AttachedList.List),
  Mode=OneWay}">
  <wpftest2:AttachedList.Items>
    <system:String>Foo</system:String>
    <system:String>Bar</system:String>
    <system:String>Zot</system:String>
  </wpftest2:AttachedList.Items>
</ListBox>


- при парсинге пути, заданного в markup extension, всё равно работает рефлекшн, и ведется поиск указанного в пути DP, и там надо указывать именно имя DP, а не имя метода доступа - иначе будет ошибка:
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
<ListBox
  ItemsSource="{Binding RelativeSource={RelativeSource Self},
  Path=(wpftest2:AttachedList.Items),
  Mode=OneWay}">
  <wpftest2:AttachedList.Items>
    <system:String>Foo</system:String>
    <system:String>Bar</system:String>
    <system:String>Zot</system:String>
  </wpftest2:AttachedList.Items>
</ListBox>

Код: plaintext
1.
2.
3.
4.
5.
Unhandled Exception: System.Windows.Markup.XamlParseException: 'Provide value on 'System.Windows.Baml2006.TypeConverterMarkupExtension'
   threw an exception.' Line number '16' and line position '4'. --->
   System.InvalidOperationException: Property path is not valid. 'AttachedList' does not have a public property named 'Items'.
   at System.Windows.PropertyPath.ResolvePropertyName(String name, Object item, Type ownerType, Object context, Boolean throwOnError)
   at System.Windows.PropertyPath.ResolvePathParts(ITypeDescriptorContext typeDescriptorContext)
   at System.Windows.PropertyPath..ctor(String path, ITypeDescriptorContext typeDescriptorContext)
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39949521
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Сон Веры Павловны , Спасибо!
Пока ясно.
Теперь придумаю куда практически это можно применить.
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39949871
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Кстати, проверил - этот хак с разными именами самой DP и именами методов доступа/свойств в классе работает и для обычных (не attached) DP.
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39949876
vb_sub
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В каких практических ситуациях может пригодиться списочное атач-свойство?
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39951090
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
vb_sub
В каких практических ситуациях может пригодиться списочное атач-свойство?

Используете Interaction: Triggers и Behaviors ?

Это пример списочных AP.

Сейчас для своей реализации хочу сделать биндинг всплывающих RoutedCommand.
Но споткнулся об область видимости привязок.
Разбираюсь как это реализовано в Interaction.Behaviors
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39951091
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Сон Веры Павловны
Кстати, проверил - этот хак с разными именами самой DP и именами методов доступа/свойств в классе работает и для обычных (не attached) DP.

Шикарно!
Это может пригодиться.
Хотя у обычных DP свойств есть конструктор экземпляра в котором можно сделать инициализацию свойства экземпляра.
Но как альтернативный вариант, вполне может подойти.

Этот момент, для AP-свойств реализован крайне неудобно и не очевидно.
MS надо было предусмотреть способ нормальной инициализации таких свойств.
Но мы ей не указ...
...
Рейтинг: 0 / 0
Освоение Attached Properties: Списочное свойство
    #39953759
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Roman Mejtes , Сон Веры Павловны , "сваял" я код для практического использования такого свойства.
Если будет возможность, прокомментируйте, пожпалуйста.

Применил такое свойство для биндинга всплыающих RoutedComman к командам VM.

Для начала создал элемент для привязки RoutedComman и команды VM.
Первая часть, где объявляются два свойства соответствующего типа и создаётся экземпляр CommandBinding.
К событиям CommandBinding подсоединяются защищённые методы в которых идёт обращение к полям-делегатам хранящих методы привязанной команды.

Код: 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.
	/// <summary>Creating CommandBinding on Received RoutedCommand and ICommand</summary>
	public partial class RoutedCommandBinding : Freezable
	{
		#region Property Declaration
		/// <summary>Binding for an popup RoutedCommand</summary>
		public RoutedCommand RoutedCommand
		{
			get { return (RoutedCommand)GetValue(RoutedCommandProperty); }
			set { SetValue(RoutedCommandProperty, value); }
		}

		// Using a DependencyProperty as the backing store for RoutedCommand.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty RoutedCommandProperty =
			DependencyProperty.Register(nameof(RoutedCommand), typeof(RoutedCommand), typeof(RoutedCommandBinding),
				new PropertyMetadata(null, RoutedCommandChanged));

		/// <summary>Binding for an executable ICommand</summary>
		public ICommand Command
		{
			get { return (ICommand)GetValue(CommandProperty); }
			set { SetValue(CommandProperty, value); }
		}

		// Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty CommandProperty =
			DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(RoutedCommandBinding),
				new PropertyMetadata(null, CommandChanged));

		/// <summary>Binding for an Handled completion</summary>
		public bool Handled
		{
			get { return (bool)GetValue(HandledProperty); }
			set { SetValue(HandledProperty, value); }
		}

		// Using a DependencyProperty as the backing store for Handled.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty HandledProperty =
			DependencyProperty.Register(nameof(Handled), typeof(bool), typeof(RoutedCommandBinding), new PropertyMetadata(true));

		/// <summary>Customized Instance CommandBinding</summary>
		public CommandBinding CommandBinding { get; }
		public static RoutedCommand EmptyCommand { get; } = new RoutedCommand("Empty",typeof(RoutedCommandBinding));

		public RoutedCommandBinding()
		{
			CommandBinding = new CommandBinding(EmptyCommand, ExecuteRoutedMethod, CanExecuteRoutedMethod);
		}

		protected override Freezable CreateInstanceCore()
		{
			throw new System.NotImplementedException();
		}

		#endregion
	}



Вторая часть с методами обратного вызова.
При изменении значений свойства RoutedComman новое значения записываются в свойство экземпляра CommandBinding.Command.
При изменении значения привязанной команды VM, в делегаты записывается ссылка на методы этой команды.

Код: 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.
59.
60.
61.
62.
63.
64.
65.
66.
	public partial class RoutedCommandBinding 
	{
		#region Methods Declaration

		/// <summary>Default CanExecute Method.</summary>
		/// <param name="parameter">Command Parameter.</param>
		/// <returns>Always <see langword="true"/>.</returns>
		public static bool CanExecuteDefault(object parameter) => true;

		/// <summary>Default Execute Method.</summary>
		/// <param name="parameter">Command Parameter.</param>
		/// <remarks>Empty body.</remarks>
		public static void ExecuteDefault(object parameter) { }

		/// <summary>Delegate for CanExecute.</summary>
		protected Func<object, bool> canExecuteDelegate = CanExecuteDefault;

		/// <summary>Delegate for Execute.</summary>
		protected Action<object> executeDelegate = ExecuteDefault;

		/// <summary>Method for CommandBinding.CanExecuteRouted.</summary>
		/// <param name="sender">The command target that is invoking the handler.</param>
		/// <param name="e">The event data.</param>
		protected virtual void CanExecuteRoutedMethod(object sender, CanExecuteRoutedEventArgs e)
		{
			e.Handled = Handled;
			e.CanExecute = canExecuteDelegate(e.Parameter);
		}

		/// <summary>Method for CommandBinding.ExecuteRouted.</summary>
		/// <param name="sender">The command target that is invoking the handler.</param>
		/// <param name="e">The event data.</param>
		protected virtual void ExecuteRoutedMethod(object sender, ExecutedRoutedEventArgs e)
		{
			e.Handled = Handled;
			executeDelegate(e.Parameter);
		}
		#endregion

		#region Callback methods Declaration

		/// <summary>Static Callback Method When Changing RoutedCommand Value</summary>
		/// <param name="d">The object in which the value has changed</param>
		/// <param name="e">Change parameters</param>
		private static void RoutedCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
			=> ((RoutedCommandBinding)d).CommandBinding.Command = (RoutedCommand)e.NewValue;

		/// <summary>Static Callback Method When Changing Command Value</summary>
		/// <param name="d">The object in which the value has changed</param>
		/// <param name="e">Change parameters</param>
		private static void CommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			RoutedCommandBinding dd = (RoutedCommandBinding)d;
			if (e.NewValue is ICommand newCommand)
			{
				dd.canExecuteDelegate = newCommand.CanExecute;
				dd.executeDelegate = newCommand.Execute;
			}
			else
			{
				dd.canExecuteDelegate = CanExecuteDefault;
				dd.executeDelegate = ExecuteDefault;
			}
		}
		#endregion
	}



Коллекция для RoutedCommandBinding.
В конструкторе надо передать ссылку на CommandBinding.
Она запоминается в свойстве только для чтения.
И при изменении коллекции, вносятся соответствующие изменения в коллекцию CommandBinding
Код: 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.
	/// <summary>Collection for RoutedCommandBinding</summary>
	public class RoutedCommandBindingCollection : FreezableCollection<RoutedCommandBinding>
	{
		/// <summary>Linked CommandBindingCollection</summary>
		public CommandBindingCollection CommandBindingCollection { get; }

		/// <summary>The only constructor</summary>
		/// <param name="commandBindingCollection">Linked CommandBindingCollection</param>
		/// <exception cref="commandBindingCollection">Thrown when null</exception>
		public RoutedCommandBindingCollection(CommandBindingCollection commandBindingCollection)
		{
			CommandBindingCollection = commandBindingCollection ?? throw new ArgumentNullException(nameof(commandBindingCollection));
			INotifyCollectionChanged notifyCollection = this;
			notifyCollection.CollectionChanged += CollectionChanged;
		}

		private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			if (e.OldItems?.Count > 0)
				foreach (RoutedCommandBinding commandBinding in e.OldItems)
					CommandBindingCollection.Remove(commandBinding.CommandBinding);

			if (e.NewItems?.Count > 0)
				foreach (RoutedCommandBinding commandBinding in e.NewItems)
					CommandBindingCollection.Add(commandBinding.CommandBinding);
		}

	}



Само AP-свойство
Код: 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.
	/// <summary>Class with Attachable Property for bound RoutedCommands</summary>
	public class RoutedCommandBindings : Freezable
	{
		/// <summary>Getting the RoutedCommand Collection</summary>
		/// <param name="obj">The object to which the property is attached</param>
		/// <returns>RoutedCommandBinding Collection</returns>
		/// <exception cref="obj">Thrown when not a UIElement or ContentElement</exception>
		public static RoutedCommandBindingCollection GetRoutedCommandBindings(DependencyObject obj)
		{
			RoutedCommandBindingCollection routedCollection = (RoutedCommandBindingCollection)obj.GetValue(RoutedCommandBindingCollectionProperty);
			if (routedCollection == null)
			{
				CommandBindingCollection commandCollection;
				if (obj is UIElement element)
					commandCollection = element.CommandBindings;
				else if (obj is ContentElement content)
					commandCollection = content.CommandBindings;
				else
					throw new ArgumentException("There must be an UIElement or ContentElement", nameof(obj));

				obj.SetValue(RoutedCommandBindingCollectionProperty, routedCollection = new RoutedCommandBindingCollection(commandCollection));
			}

			return routedCollection;
		}

		protected override Freezable CreateInstanceCore()
		{
			throw new NotImplementedException();
		}

		// Using a DependencyProperty as the backing store for RoutedCommandBindingCollection.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty RoutedCommandBindingCollectionProperty =
			DependencyProperty.RegisterAttached("ShadowRoutedCommandBindings", typeof(RoutedCommandBindingCollection), typeof(RoutedCommandBindings), new PropertyMetadata(null));

	}



Пример его использования
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
	public class ViewModel
	{
		public ICommand Command { get; } = new RelayCommand
		(
			p => MessageBox.Show((string)p),
			p => !string.IsNullOrWhiteSpace(p as string)
		);

		public string Text { get; set; } = "Свойство";
	}


Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
	public static class MyRoutedComands
	{
		public static RoutedCommand First { get; }
			= new RoutedUICommand("Первый", nameof(First), typeof(MyRoutedComands));

		public static RoutedCommand Second { get; }
			= new RoutedUICommand("Второй", nameof(Second), typeof(MyRoutedComands));
	}


Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <StackPanel>
        <propa:RoutedCommandBindings.RoutedCommandBindings>
            <propa:RoutedCommandBinding Command="{Binding Command}" RoutedCommand="{x:Static local:MyRoutedComands.First}"/>
            <propa:RoutedCommandBinding Command="{Binding Command}" RoutedCommand="{x:Static local:MyRoutedComands.Second}"/>
        </propa:RoutedCommandBindings.RoutedCommandBindings>
        <Menu Margin="10,0,10,10">
            <MenuItem Command="{x:Static local:MyRoutedComands.First}" CommandParameter="{Binding Text, ElementName=textBox}"/>
            <MenuItem Command="{x:Static local:MyRoutedComands.Second}" CommandParameter="{Binding Text, ElementName=textBoxSec}"/>
        </Menu>
        <TextBox x:Name="textBox" Text="Первый параметр" />
        <TextBox x:Name="textBoxSec" Text="Второй параметр" />
    </StackPanel>
</Window>



[youtube=
YouTube Video
...
Рейтинг: 0 / 0
18 сообщений из 18, страница 1 из 1
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / Освоение Attached Properties: Списочное свойство
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]