#c# #wpf #inheritance #properties #triggers
#c# #wpf #наследование #свойства #триггеры
Вопрос:
Когда у меня есть элемент управления A
, содержащий элемент управления B
, есть свойства Prop
, которые наследуются. Это означает, B.Prop
что автоматически примет значение, A.Prop
если B.Prop
оно явно не задано. Насколько я знаю, IsEnabled
есть такое свойство.
Теперь у меня ситуация, когда я устанавливаю значение B.IsEnabled
явно, и все равно оно перезаписывается значением A.IsEnabled
. Почему это так, и как я могу это исправить?
В этой ситуации A
это панель стека и B
текстовое поле:
<StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding InDisableMode}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBox Text="some text">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding InDisableMode}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
Приведенный выше фрагмент XAML имеет DataContext, установленный для my ViewModel. ViewModel содержит свойство InDisableMode
, которое является bool
. Когда это так false
, все происходит так, как ожидалось: StackPanel включена, а текстовое поле отключено.
Но когда InDisableMode
это true
так, и панель стека, и текстовое поле отключены, хотя оба триггера должны срабатывать!
Я знаю, что могу привязать IsEnabled
данные InDisableMode
к обоим элементам управления (непосредственно в текстовом поле, в стековой панели с помощью дополнительного конвертера). Я не пробовал, работает ли это, так как я все равно хочу сделать это с помощью триггеров.
Редактировать:
Смысл отключения StackPanel заключается в том, чтобы легко отключить все его дочерние элементы (кроме текстового поля, которое я хочу включить вместо этого). Любые другие идеи, как решить эту задачу без изменения отношения родитель-потомок или создания новых элементов управления? На данный момент единственный способ, который я вижу, — отключить все дочерние элементы, кроме текстового поля, один за другим…
Ответ №1:
Если вы отключите элемент управления, его дочерние элементы будут отключены. Поскольку a StackPanel
не является интерактивным, нет никаких причин для его отключения, кроме как для отключения его интерактивных дочерних элементов.
Если вы хотите включить элемент управления A, в то время как его родительский элемент B отключен, вы не можете этого сделать. B не может быть родительским, если вы хотите включить A, пока B отключен.
Для обходного пути вы можете поместить их оба в a Grid
с TextBox
определенным последним, чтобы наложить TextBox
поверх StackPanel
. Тогда оно будет находиться в области StackPanel
‘s, но не будет дочерним элементом StackPanel
.
Ответ №2:
Это происходит потому UIElement.IsEnabled
, что свойство использует принуждение к значению, наследуя значение от своего родителя. Это делается с помощью CoerceValueCallback
. Принуждение к значению занимает первое место в списке приоритетов настройки свойств зависимостей.
Итак, чтобы переопределить это поведение, у нас есть два варианта. Во-первых, для использования AddOwner()
для регистрации нашего типа в качестве нового владельца IsEnabled
свойства. Во-вторых, для переопределения метаданных с помощью OverrideMetadata()
. Этот второй метод будет работать, только если вы наследуете напрямую от UIElement
.
Итак, допустим, мы хотим, чтобы наше Button
поведение было другим, мы должны создать новое Button
, подобное приведенному ниже :
public class CButton : Button
{
public static readonly DependencyProperty IsEnabled;
static CButton()
{
IsEnabled = UIElement.IsEnabledProperty.AddOwner(typeof(CButton),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.None,
UIElement.IsEnabledProperty.DefaultMetadata.PropertyChangedCallback,
new CoerceValueCallback(IsEnabledCoerceCallback)));
}
private static object IsEnabledCoerceCallback(DependencyObject d, object baseValue)
{
return (bool) baseValue;
}
}
Здесь мы возвращаем присвоенное значение в том виде, в каком оно есть IsEnabledCoerceCallback
. Перед возвратом вы также можете ввести поведение: если пользователь не предоставляет никакого значения для IsEnabled
, затем используйте унаследованное значение от родительского, иначе используйте CButton.IsEnabled
назначенное пользователем значение.
Кстати, попробуйте установить null
вместо new CoerceValueCallback(IsEnabledCoerceCallback)
, посмотрите, что произойдет.
Комментарии:
1. «Это происходит потому, что UIElement. Свойство IsEnabled использует принуждение к значению, наследуя значение от своего родителя «. — но если дочерний элемент всегда наследует значение от своего родительского элемента, почему возможно включить родительский элемент, а дочерний элемент отключить?
2. @Kjara Наследование и перезапись — это два разных случая. Принудительное означает, что вы хотите перезаписать поведение по умолчанию. Label. FontSize = 20 изменит размер шрифта всех текстовых блоков внутри него, но если вы установите размер шрифта определенного, это будет соблюдено.