У меня есть UserControl, который я использую в своем MainWindow следующим образом:
<userControls:NoMarkupControl/>
ViewModel моего MainWindow содержит это свойство:
private string _exampleText = "example";
public string ExampleText
{
get { return _exampleText; }
set
{
_exampleText = value;
OnPropertyChanged();
}
}
внутри UserControl я привязываю свою ViewModel к этому свойству:
<TextBlock Text = "{Binding ExampleText}"/>
в результате «пример» отображается при запуске приложения. Все работает.
Теперь у меня есть MarkupExtension:
public class ExampleTextExtension : MarkupExtension
{
private static readonly List<DependencyProperty> StorageProperties = new List<DependencyProperty>();
private readonly object _parameter;
private DependencyProperty _dependencyProperty;
public ExampleTextExtension(object parameter)
{
_parameter = parameter;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
DependencyObject targetObject;
if (target?.TargetObject is DependencyObject dependencyObject &&
target.TargetProperty is DependencyProperty)
{
targetObject = dependencyObject;
}
else
{
return this;
}
_dependencyProperty = SetUnusedStorageProperty(targetObject, _parameter);
return GetLocalizedText((string)targetObject.GetValue(_dependencyProperty));
}
private static string GetLocalizedText(string text)
{
return text == null ? null : $"markup: {text}";
}
private static DependencyProperty SetUnusedStorageProperty(DependencyObject obj, object value)
{
var property = StorageProperties.FirstOrDefault(p => obj.ReadLocalValue(p) == DependencyProperty.UnsetValue);
if (property == null)
{
property = DependencyProperty.RegisterAttached("Storage" + StorageProperties.Count, typeof(object), typeof(ExampleTextExtension), new PropertyMetadata());
StorageProperties.Add(property);
}
if (value is MarkupExtension markupExtension)
{
var resolvedValue = markupExtension.ProvideValue(new ServiceProvider(obj, property));
obj.SetValue(property, resolvedValue);
}
else
{
obj.SetValue(property, value);
}
return property;
}
private class ServiceProvider : IServiceProvider, IProvideValueTarget
{
public object TargetObject { get; }
public object TargetProperty { get; }
public ServiceProvider(object targetObject, object targetProperty)
{
TargetObject = targetObject;
TargetProperty = targetProperty;
}
public object GetService(Type serviceType)
{
return serviceType.IsInstanceOfType(this) ? this : null;
}
}
}
Опять же, у меня есть UserControl, который я использую в своем MainWindow следующим образом:
<userControls:MarkupControl/>
ViewModel моего MainWindow остается таким же, как указано выше.
внутри UserControl я привязываюсь к своему свойству TextBlock Text следующим образом:
<TextBlock Text = "{markupExtensions:ExampleText {Binding ExampleText}}"/>
в результате мой UserControl ничего не отображает. Я бы ожидал, что отобразится «разметка: пример»
Привязка как-то не работает в этом случае.
Кто-нибудь знает, как это исправить?
он работает при таком использовании (свойство зависимости MarkupText создается в пользовательском элементе управления):
<userControls:MarkupControl MarkupText = {markupExtensions:ExampleText {Binding ExampleText}}/>
<TextBlock Text = "{Binding Text, ElementName=MarkupControl}"/>
Вы должны установить переданное в Binding свойство зависимости, чтобы активировать его. Это механизм привязки, который фактически выполняет всю работу по связыванию целевого свойства с исходным свойством. Механизм привязки является частью инфраструктуры свойств зависимостей. Вот почему цель Binding должна быть свойством зависимости. Вам необходимо создать промежуточное свойство зависимости для разрешения Binding. Обработайте события Binding SourceUpdated и TargetUpdated, чтобы получить обновленное значение. Затем обработайте/обработайте его и отправьте в цель вашего пользовательского расширения разметки.
Чтобы прикрепить Binding, ваше промежуточное свойство должно быть определено DependencyObject. Это означает, что вам нужно создать специальный класс для разрешения привязки.
@Andy Я создал это расширение разметки, чтобы показать, что не работает, мое настоящее расширение разметки обрабатывает какое-то изменение языка. Я мог бы сделать это и на виртуальной машине, но я думаю, что расширение разметки делает его чище и (если работает) проще в использовании.
@BionicCode Я не уверен, что понимаю тебя. Я думал, что уже использую свойство зависимости: property = DependencyProperty.RegisterAttached("Storage" + StorageProperties.Count, typeof(object), typeof(ExampleTextExtension), new PropertyMetadata());
и здесь я связываю dp с объектом зависимости: var resolvedValue = markupExtension.ProvideValue(new ServiceProvider(obj, property)); obj.SetValue(property, resolvedValue);
можете ли вы опубликовать пример или попытаться указать, что вы имеете в виду, пожалуйста? Привязка в основном работает, но не в случае, указанном в моем вопросе.
Под «каким-то изменением языка» вы буквально подразумеваете увидеть все на французском, немецком или английском языке - локализацию? Динамический ресурс и объединение словаря ресурсов для каждого языка — хороший способ сделать это.
@ Энди, да на твой вопрос, но нет на твое предложение. все тексты хранятся в базе данных и доступны через сервис...
Почему это означает, что вы не можете объединить их как ресурсы? Вы можете создать словарь ресурсов в коде или установить уже объединенные значения, или объединить плоский файл, или объединить строку и т. д. Это расширение разметки не кажется мне хорошей идеей. Я думаю, проблема в том, что у вас есть дп в одном классе, а вы привязываете его к другому.
Другим вариантом является безликий элемент управления, предоставляющий dp string[] или наблюдаемый словарь строк. Вы можете использовать обычную привязку и загружать ее так, как вам нравится.
@ Энди, я не уверен, как мне получить тексты из этой базы данных (принадлежит третьей стороне, у меня есть доступ только через SDK), а затем использовать ее в качестве ресурса. Можете ли вы объяснить этот подход немного дальше?
Если вы вообще можете их прочитать, то вы можете прочитать и сохранить как обычный некомпилированный словарь ресурсов. Которые затем могут быть объединены в любое время. Хотя что я объясняю? То, как вы получаете эти строки, кажется, что это может иметь значение в той или иной степени. В любом случае. У вас тут странный дизайн.
@Энди, а, теперь я понял, что ты имеешь в виду... не думал об этом, спасибо
Во-первых, вам нужно реорганизовать расширение, чтобы упростить реализацию. Здесь вам не нужен статический контекст. Избавление от контекста класса сделает отслеживание созданных присоединенных свойств устаревшим. Вы можете безопасно удалить связанную коллекцию. В вашем случае более эффективно хранить значения в контексте экземпляра. Прикрепленные свойства также являются удобным решением для хранения значений для каждого экземпляра, особенно в контексте static
.
Во-вторых, у вас проблема со временем. При первом вызове расширения Binding
не инициализируется должным образом: оно не предоставляет окончательное значение Binding.Source
.
Кроме того, ваша текущая реализация не поддерживает изменение свойств.
Чтобы исправить это, вам нужно будет отслеживать обновления Binding.Target
, когда значение отправляется из Binding.Source
(по умолчанию BindingMode.OneWay
). Вы можете добиться этого, прослушав событие Binding.TargetUpdated
(как указано в моем предыдущем комментарии) или зарегистрировав обработчик изменения свойства с присоединенным свойством (рекомендуется).
Чтобы поддерживать двустороннюю привязку, вам также необходимо отслеживать целевое свойство (свойство, которому назначен ваш MarkupExtension
).
Исправленная и улучшенная версия может выглядеть следующим образом:
public class ExampleTextExtension : MarkupExtension
{
private static DependencyProperty ResolvedBindingSourceValueProperty = DependencyProperty.RegisterAttached(
"ResolvedBindingSourceValue",
typeof(object),
typeof(ExampleTextExtension),
new PropertyMetadata(default(object), OnResolvedBindingSourceValueChanged));
// Use attached property to store the target object
// for reference from a static context without dealing with class level members that are shared between instances.
private static DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached(
"TargetProperty",
typeof(DependencyProperty),
typeof(ExampleTextExtension),
new PropertyMetadata(default));
private Binding Binding { get; }
// Accept BindingBase to support MultiBinding etc.
public ExampleTextExtension(Binding binding)
{
this.Binding = binding;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTargetService = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (provideValueTargetService?.TargetObject is not DependencyObject targetObject
|| provideValueTargetService?.TargetProperty is not DependencyProperty targetProperty)
{
return this;
}
targetObject.SetValue(ExampleTextExtension.TargetPropertyProperty, targetProperty);
AttachBinding(targetObject);
return string.Empty;
}
private static string GetLocalizedText(string text)
=> String.IsNullOrWhiteSpace(text)
? string.Empty
: $"markup: {text}";
// By now, only supports OneWay binding
private void AttachBinding(DependencyObject targetObject)
{
switch (this.Binding.Mode)
{
case BindingMode.OneWay:
case BindingMode.Default:
HandleOneWayBinding(targetObject); break;
default: throw new NotSupportedException();
}
}
private void HandleOneWayBinding(DependencyObject targetObject)
{
BindingOperations.SetBinding(targetObject, ExampleTextExtension.ResolvedBindingSourceValueProperty, this.Binding);
}
// Property changed handler to update the target of this extension
// with the localized value
private static void OnResolvedBindingSourceValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string localizedText = GetLocalizedText(e.NewValue as string);
var targetProperty = d.GetValue(ExampleTextExtension.TargetPropertyProperty) as DependencyProperty;
d.SetValue(targetProperty, localizedText);
}
}
Есть лучшие решения для введения локализации без ущерба для общего синтаксиса или устаревшего кода. Например, введение этого MarkupExtension
в существующий код нарушит этот код, поскольку все соответствующие привязки данных (C# и XAML) должны быть изменены.
Наиболее распространенным подходом является использование вспомогательных сборок и локализованных ресурсов. Вместо преобразования текстовых значений во время привязки данных вы должны локализовать источник значения напрямую (чтобы Binding
передавал уже локализованные значения).
Другими словами, убедитесь, что источник данных локализован. Пусть источник привязки предоставляет текст, извлекая его из локализованного репозитория.
Спасибо. часть OnResolvedBindingSourceValueChanged была тем, чего не хватало и чего я не получил. так что это был ответ, который я искал...
у меня уже была остальная часть (упрощение) вашей реализации, но я добавил отслеживание через коллекцию после того, как прочитал эту статью: singulink.com/codeindex/post/… где автор заявил: хранить свои значения в своей цели элемент, и мы не регистрируем без необходимости дополнительные прикрепленные свойства или утечку памяти, поскольку элементы пользовательского интерфейса создаются и уничтожаются снова и снова, и регистрируется все больше и больше свойств, можете ли вы объяснить, почему это не проблема в моем случае?
в реализации, которая у меня была раньше (примерно так: stackoverflow.com/a/37667896), вместо привязки использовалась bindingbase, и возвращаемое значение в ProvideValue также было другим. можешь объяснить разницу?
я посмотрю на то, что вы предложили. возможно ли изменить язык во время выполнения с этими сателлитными сборками? это обязательно в моем приложении.
Вам не нужна часть коллекции. Автор использовал его, чтобы разрешить передачу неограниченного количества аргументов расширению разметки. Он создает выделенное свойство для каждого аргумента. Я предполагаю, что гораздо лучшим решением было бы создать одно прикрепленное свойство и сохранить аргументы в списке. Хранение такого количества свойств зависимостей в статической коллекции эффективно создает ненужные «утечки памяти», поскольку сборщик мусора не может собирать статические ссылки или ссылки, на которые ссылаются статические ссылки. Я предполагаю, что он был в курсе и поэтому пытался повторно использовать как можно больше свойств.
Вместо этого он должен был хранить список значений в одном прикрепленном свойстве. Поскольку ваше расширение принимает только одно значение для каждого экземпляра, вам определенно не нужна часть коллекции. Одно прикрепленное свойство для разрешения Binding делает свое дело. ==
BindingBase — это базовый класс Binding, MultiBinding и PriorityyBinding. Чтобы обеспечить совместимость расширения со всеми типами привязок, вы должны написать код, ориентированный на общий базовый класс (BindingBase). "и доходность в ProvideValue также была другой. Вы можете объяснить разницу?" - что вы имеете в виду под "отдача тоже была другой" по сравнению с чем? ===
большое спасибо за ваши объяснения ... то, что я имел в виду под «возвратом было другим», было возвратом в функции ProvideValue
. в решении, которое я связал return param1InnerBinding.ProvideValue(serviceProvider); // return binding to Param1.SomeInnerProperty
, было возвращено. так как вы просто возвращаете string.empty
, я не понимаю, для чего используется возврат ProvideValue
, потому что в вашей реализации это кажется ненужным...
Зачем расширение разметки? Вместо динамического ресурса или просто свойства в модели представления?