<Grid IsEnabled="{Binding IsEditable}">
...
</Grid>
If only there was a way to stop TextBoxes from inheriting their parent's IsEnabled. Instead I would like the text box to become read only when the container is disabled. But if the text box has IsReadOnly bound, the container needs to honor that to.
Here it is...
Start a new Visual Studio project called IsReadOnlyTextBox using Visual Basic, .Net Framework.
Add a new class called CustomGrid and another called CustomTextBox. For a full implementation of this feature we need to subclass all potential containers and all potential controls.
Let's start with the control. We need to break the inheritance of IsEnabled and also intercept changes to IsReadOnly.
Shared Sub New()
IsEnabledProperty.OverrideMetadata(GetType(CustomTextBox),
New UIPropertyMetadata(defaultValue:=True,
propertyChangedCallback:=Sub(a, b)
End Sub,
coerceValueCallback:=Function(a, b) b))
IsReadOnlyProperty.OverrideMetadata(GetType(CustomTextBox),
New FrameworkPropertyMetadata(
defaultValue:=False,
propertyChangedCallback:=AddressOf IsReadOnlyChanged,
coerceValueCallback:=AddressOf IsReadOnlyCoerced
))
End Sub
The whole CustomTextBox class looks like this.
Public Class CustomTextBox
Inherits TextBox
Public Property IsReadOnlyBackup As Boolean = False
Public Property IsSetting As Boolean = False
Shared Sub New()
IsEnabledProperty.OverrideMetadata(GetType(CustomTextBox),
New UIPropertyMetadata(defaultValue:=True,
propertyChangedCallback:=Sub(a, b)
End Sub,
coerceValueCallback:=Function(a, b) b))
IsReadOnlyProperty.OverrideMetadata(GetType(CustomTextBox),
New FrameworkPropertyMetadata(
defaultValue:=False,
propertyChangedCallback:=AddressOf IsReadOnlyChanged,
coerceValueCallback:=AddressOf IsReadOnlyCoerced
))
End Sub
Shared Sub IsReadOnlyChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim tb As CustomTextBox = TryCast(d, CustomTextBox)
If Not tb.IsSetting Then
Debug.WriteLine($"Setting IsReadOnlyBackup {e.NewValue}")
tb.IsReadOnlyBackup = Convert.ToBoolean(e.NewValue)
End If
End Sub
Shared Function IsReadOnlyCoerced(d As DependencyObject, value As Object) As Object
Return value
End Function
Public Sub SetIsReadOnly(value As Boolean)
IsSetting = True
Debug.WriteLine($"SetIsReadOnly {value}")
If value Then
Me.IsReadOnly = True
Else
Me.IsReadOnly = IsReadOnlyBackup
End If
Debug.WriteLine($"IsReadOnly is now {Me.IsReadOnly}")
IsSetting = False
End Sub
End Class
We need to subclass the container, in this case Grid, to intercept changes to IsEnabled and apply the change to its children. It looks like this...
Public Class CustomTextBox
Inherits TextBox
Public Property IsReadOnlyBackup As Boolean = False
Public Property IsSetting As Boolean = False
Shared Sub New()
IsEnabledProperty.OverrideMetadata(GetType(CustomTextBox),
New UIPropertyMetadata(defaultValue:=True,
propertyChangedCallback:=Sub(a, b)
End Sub,
coerceValueCallback:=Function(a, b) b))
IsReadOnlyProperty.OverrideMetadata(GetType(CustomTextBox),
New FrameworkPropertyMetadata(
defaultValue:=False,
propertyChangedCallback:=AddressOf IsReadOnlyChanged,
coerceValueCallback:=AddressOf IsReadOnlyCoerced
))
End Sub
Shared Sub IsReadOnlyChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim tb As CustomTextBox = TryCast(d, CustomTextBox)
If Not tb.IsSetting Then
Debug.WriteLine($"Setting IsReadOnlyBackup {e.NewValue}")
tb.IsReadOnlyBackup = Convert.ToBoolean(e.NewValue)
End If
End Sub
Shared Function IsReadOnlyCoerced(d As DependencyObject, value As Object) As Object
Return value
End Function
Public Sub SetIsReadOnly(value As Boolean)
IsSetting = True
Debug.WriteLine($"SetIsReadOnly {value}")
If value Then
Me.IsReadOnly = True
Else
Me.IsReadOnly = IsReadOnlyBackup
End If
Debug.WriteLine($"IsReadOnly is now {Me.IsReadOnly}")
IsSetting = False
End Sub
End Class
<Window x:Class="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:IsReadOnlyTextBox"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="Inheriting IsEnabled" SizeToContent="WidthAndHeight">
<StackPanel Orientation="Vertical" Margin="10">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsGridEnabled}" Content="Enable Grid"/>
<CheckBox IsChecked="{Binding IsCheckboxEnabled}" Content="Checkbox is enabled" Margin="20,0,0,0"/>
<CheckBox IsChecked="{Binding IsOldTextboxReadOnly}" Content="Old TextBox is Readonly" Margin="20,0,0,0"/>
<CheckBox IsChecked="{Binding IsNewTextboxReadOnly}" Content="New TextBox is Readonly" Margin="20,0,0,0"/>
</StackPanel>
<Border Margin="10" Padding="10" BorderThickness="1" BorderBrush="Black">
<local:CustomGrid IsEnabled="{Binding IsGridEnabled}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Row="0" Grid.Column="1" Content="A check box" IsEnabled="{Binding IsCheckboxEnabled, Mode=TwoWay}"/>
<TextBox Grid.Row="1" Grid.Column="2"
Text="A regular old text box" Width="100"
IsReadOnly="{Binding IsOldTextboxReadOnly, Mode=TwoWay}"
HorizontalScrollBarVisibility="Auto"/>
<local:CustomTextBox Grid.Row="1" Grid.Column="3"
Text="A new custom text box" Width="100"
IsReadOnly="{Binding IsNewTextboxReadOnly, Mode=TwoWay}"
HorizontalScrollBarVisibility="Auto"/>
</local:CustomGrid>
</Border>
</StackPanel>
</Window>
The code behind looks like this...
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Class MainWindow
Implements INotifyPropertyChanged
Private _IsGridEnabled As Boolean = True
Public Property IsGridEnabled As Boolean
Get
Return _IsGridEnabled
End Get
Set(value As Boolean)
SetProperty(_IsGridEnabled, value)
End Set
End Property
Private _IsOldTextboxReadOnly As Boolean = False
Public Property IsOldTextboxReadOnly As Boolean
Get
Return _IsOldTextboxReadOnly
End Get
Set(value As Boolean)
SetProperty(_IsOldTextboxReadOnly, value)
End Set
End Property
Private _IsNewTextboxReadOnly As Boolean
Public Property IsNewTextboxReadOnly As Boolean
Get
Return _IsNewTextboxReadOnly
End Get
Set(value As Boolean)
SetProperty(_IsNewTextboxReadOnly, value)
End Set
End Property
Private _IsCheckboxEnabled As Boolean = True
Public Sub New()
Debug.WriteLine("-------------------")
InitializeComponent()
End Sub
Public Property IsCheckboxEnabled As Boolean
Get
Return _IsCheckboxEnabled
End Get
Set(value As Boolean)
SetProperty(_IsCheckboxEnabled, value)
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Function SetProperty(Of T)(ByRef storage As T, value As T, <CallerMemberName> Optional PropertyName As String = "") As Boolean
If Object.Equals(storage, value) Then Return False
storage = value
NotifyPropertyChanged(PropertyName)
Return True
End Function
Public Sub NotifyPropertyChanged(PropertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
End Sub
End Class
The result looks like this...
When the grid is disabled the new custom text control goes into Read Only mode.
No comments:
Post a Comment