次の例は、TextBoxが空の場合にTextBox
にプレースホルダー テキストを表示する方法を示しています。
TextBox
にテキストがある場合、プレースホルダー テキストは非表示になります。 プレースホルダー テキストは、ユーザーが TextBox
が想定する入力の種類を理解するのに役立ちます。
この記事では、次の方法について説明します。
- プレースホルダー テキストを提供する添付プロパティを作成します。
- プレースホルダー テキストを表示する装飾を作成します。
- 添付プロパティを TextBox コントロールに追加します。
添付プロパティを作成する
添付プロパティを使用すると、コントロールに値を追加できます。 この機能は、コントロールに Grid.Row
プロパティや Panel.ZIndex
プロパティを設定する場合など、WPF で多くを使用します。 詳細については、「 添付プロパティの概要」を参照してください。 この例では、添付プロパティを使用してプレースホルダー テキストを TextBoxに追加します。
TextBoxHelper
という名前の新しいクラスをプロジェクトに追加して開きます。次の名前空間を追加します。
using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media;
Imports System.Linq Imports System.Security.Cryptography Imports System.Windows Imports System.Windows.Controls Imports System.Windows.Documents Imports System.Windows.Media
Placeholder
という名前の新しい依存関係プロパティを作成します。この依存関係プロパティは、プロパティ変更コールバック デリゲートを使用します。
public static string GetPlaceholder(DependencyObject obj) => (string)obj.GetValue(PlaceholderProperty); public static void SetPlaceholder(DependencyObject obj, string value) => obj.SetValue(PlaceholderProperty, value); public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.RegisterAttached( "Placeholder", typeof(string), typeof(TextBoxHelper), new FrameworkPropertyMetadata( defaultValue: null, propertyChangedCallback: OnPlaceholderChanged) );
Public Shared Function GetPlaceholder(obj As DependencyObject) As String Return obj.GetValue(PlaceholderProperty) End Function Public Shared Sub SetPlaceholder(obj As DependencyObject, value As String) obj.SetValue(PlaceholderProperty, value) End Sub Public Shared ReadOnly PlaceholderProperty As DependencyProperty = DependencyProperty.RegisterAttached( "Placeholder", GetType(String), GetType(TextBoxHelper), New FrameworkPropertyMetadata( defaultValue:=Nothing, propertyChangedCallback:=AddressOf OnPlaceholderChanged) )
添付プロパティを
OnPlaceholderChanged
と統合するTextBox
メソッドを作成します。private static void OnPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is TextBox textBoxControl) { if (!textBoxControl.IsLoaded) { // Ensure that the events are not added multiple times textBoxControl.Loaded -= TextBoxControl_Loaded; textBoxControl.Loaded += TextBoxControl_Loaded; } textBoxControl.TextChanged -= TextBoxControl_TextChanged; textBoxControl.TextChanged += TextBoxControl_TextChanged; // If the adorner exists, invalidate it to draw the current text if (GetOrCreateAdorner(textBoxControl, out PlaceholderAdorner adorner)) adorner.InvalidateVisual(); } }
Private Shared Sub OnPlaceholderChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs) Dim textBoxControl = TryCast(d, TextBox) If textBoxControl IsNot Nothing Then If Not textBoxControl.IsLoaded Then 'Ensure that the events are not added multiple times RemoveHandler textBoxControl.Loaded, AddressOf TextBoxControl_Loaded AddHandler textBoxControl.Loaded, AddressOf TextBoxControl_Loaded End If RemoveHandler textBoxControl.TextChanged, AddressOf TextBoxControl_TextChanged AddHandler textBoxControl.TextChanged, AddressOf TextBoxControl_TextChanged 'If the adorner exists, invalidate it to draw the current text Dim adorner As PlaceholderAdorner = Nothing If GetOrCreateAdorner(textBoxControl, adorner) Then adorner.InvalidateVisual() End If End If End Sub
添付プロパティの値が変更されたときにこのメソッドを呼び出す方法は 2 つあります。
- 添付プロパティが最初に
TextBox
に追加されると、このメソッドが呼び出されます。 このアクションは、添付プロパティがコントロールのイベントと統合される機会を提供します。 - このプロパティが変更されるたびに、装飾を無効にして、視覚的なプレースホルダー テキストを更新できます。
GetOrCreateAdorner
メソッドは、次のセクションで作成します。- 添付プロパティが最初に
TextBox
のイベント ハンドラーを作成します。private static void TextBoxControl_Loaded(object sender, RoutedEventArgs e) { if (sender is TextBox textBoxControl) { textBoxControl.Loaded -= TextBoxControl_Loaded; GetOrCreateAdorner(textBoxControl, out _); } } private static void TextBoxControl_TextChanged(object sender, TextChangedEventArgs e) { if (sender is TextBox textBoxControl && GetOrCreateAdorner(textBoxControl, out PlaceholderAdorner adorner)) { // Control has text. Hide the adorner. if (textBoxControl.Text.Length > 0) adorner.Visibility = Visibility.Hidden; // Control has no text. Show the adorner. else adorner.Visibility = Visibility.Visible; } }
Private Shared Sub TextBoxControl_Loaded(sender As Object, e As RoutedEventArgs) Dim textBoxControl As TextBox = TryCast(sender, TextBox) If textBoxControl IsNot Nothing Then RemoveHandler textBoxControl.Loaded, AddressOf TextBoxControl_Loaded GetOrCreateAdorner(textBoxControl, Nothing) End If End Sub Private Shared Sub TextBoxControl_TextChanged(sender As Object, e As TextChangedEventArgs) Dim textBoxControl As TextBox = TryCast(sender, TextBox) Dim adorner As PlaceholderAdorner = Nothing If textBoxControl IsNot Nothing AndAlso GetOrCreateAdorner(textBoxControl, adorner) Then If textBoxControl.Text.Length > 0 Then 'Control has text. Hide the adorner. adorner.Visibility = Visibility.Hidden Else 'Control has no text. Show the adorner. adorner.Visibility = Visibility.Visible End If End If End Sub
Loaded イベントは、コントロールのテンプレートが適用された後に装飾を作成できるように処理されます。 イベントが発生して装飾が作成された後でハンドラーは自身を削除します。
TextChanged イベントは、Textが値に設定されているかどうかに応じて装飾が非表示または表示されるように処理されます。
装飾を作成する
Adornerは、コントロールにバインドされ、AdornerLayerでレンダリングされるビジュアルです。 詳細情報については、「装飾の概要」を参照してください。
TextBoxHelper
クラスを開きます。次のコードを追加して、
GetOrCreateAdorner
メソッドを作成します。private static bool GetOrCreateAdorner(TextBox textBoxControl, out PlaceholderAdorner adorner) { // Get the adorner layer AdornerLayer layer = AdornerLayer.GetAdornerLayer(textBoxControl); // If null, it doesn't exist or the control's template isn't loaded if (layer == null) { adorner = null; return false; } // Layer exists, try to find the adorner adorner = layer.GetAdorners(textBoxControl)?.OfType<PlaceholderAdorner>().FirstOrDefault(); // Adorner never added to control, so add it if (adorner == null) { adorner = new PlaceholderAdorner(textBoxControl); layer.Add(adorner); } return true; }
Private Shared Function GetOrCreateAdorner(textBoxControl As TextBox, ByRef adorner As PlaceholderAdorner) As Boolean 'Get the adorner layer Dim layer As AdornerLayer = AdornerLayer.GetAdornerLayer(textBoxControl) 'If nothing, it doesn't exist or the control's template isn't loaded If layer Is Nothing Then adorner = Nothing Return False End If 'Layer exists, try to find the adorner adorner = layer.GetAdorners(textBoxControl)?.OfType(Of PlaceholderAdorner)().FirstOrDefault() 'Adorner never added to control, so add it If adorner Is Nothing Then adorner = New PlaceholderAdorner(textBoxControl) layer.Add(adorner) End If Return True End Function
このメソッドは、 Adornerを追加または取得する安全な方法を提供します。 装飾は、存在しない可能性があるコントロールの AdornerLayerに追加されるため、追加の安全性が必要です。 XAML 添付プロパティがコントロールに適用されている場合、ビジュアル ツリーを作成するためにコントロールのテンプレートがまだ適用されていないため、装飾レイヤーは存在しません。 装飾レイヤーは、コントロールが読み込まれた後に取得する必要があります。 装飾レイヤーを省略したテンプレートがコントロールに適用されている場合も、装飾レイヤーが見つからない可能性があります。
PlaceholderAdorner
クラスにTextBoxHelper
という名前の子クラスを追加します。public class PlaceholderAdorner : Adorner { public PlaceholderAdorner(TextBox textBox) : base(textBox) { } protected override void OnRender(DrawingContext drawingContext) { TextBox textBoxControl = (TextBox)AdornedElement; string placeholderValue = TextBoxHelper.GetPlaceholder(textBoxControl); if (string.IsNullOrEmpty(placeholderValue)) return; // Create the formatted text object FormattedText text = new FormattedText( placeholderValue, System.Globalization.CultureInfo.CurrentCulture, textBoxControl.FlowDirection, new Typeface(textBoxControl.FontFamily, textBoxControl.FontStyle, textBoxControl.FontWeight, textBoxControl.FontStretch), textBoxControl.FontSize, SystemColors.InactiveCaptionBrush, VisualTreeHelper.GetDpi(textBoxControl).PixelsPerDip); text.MaxTextWidth = System.Math.Max(textBoxControl.ActualWidth - textBoxControl.Padding.Left - textBoxControl.Padding.Right, 10); text.MaxTextHeight = System.Math.Max(textBoxControl.ActualHeight, 10); // Render based on padding of the control, to try and match where the textbox places text Point renderingOffset = new Point(textBoxControl.Padding.Left, textBoxControl.Padding.Top); // Template contains the content part; adjust sizes to try and align the text if (textBoxControl.Template.FindName("PART_ContentHost", textBoxControl) is FrameworkElement part) { Point partPosition = part.TransformToAncestor(textBoxControl).Transform(new Point(0, 0)); renderingOffset.X += partPosition.X; renderingOffset.Y += partPosition.Y; text.MaxTextWidth = System.Math.Max(part.ActualWidth - renderingOffset.X, 10); text.MaxTextHeight = System.Math.Max(part.ActualHeight, 10); } // Draw the text drawingContext.DrawText(text, renderingOffset); } }
Public Class PlaceholderAdorner Inherits Adorner Public Sub New(adornedElement As UIElement) MyBase.New(adornedElement) End Sub Protected Overrides Sub OnRender(drawingContext As DrawingContext) Dim textBoxControl As TextBox = DirectCast(AdornedElement, TextBox) Dim placeholderValue As String = TextBoxHelper.GetPlaceholder(textBoxControl) If String.IsNullOrEmpty(placeholderValue) Then Return End If 'Create the formatted text object Dim text As New FormattedText( placeholderValue, System.Globalization.CultureInfo.CurrentCulture, textBoxControl.FlowDirection, New Typeface(textBoxControl.FontFamily, textBoxControl.FontStyle, textBoxControl.FontWeight, textBoxControl.FontStretch), textBoxControl.FontSize, SystemColors.InactiveCaptionBrush, VisualTreeHelper.GetDpi(textBoxControl).PixelsPerDip) text.MaxTextWidth = Math.Max(textBoxControl.ActualWidth - textBoxControl.Padding.Left - textBoxControl.Padding.Right, 10) text.MaxTextHeight = Math.Max(textBoxControl.ActualHeight, 10) 'Render based on padding of the control, to try and match where the textbox places text Dim renderingOffset As New Point(textBoxControl.Padding.Left, textBoxControl.Padding.Top) 'Template contains the content part; adjust sizes to try and align the text Dim part As FrameworkElement = TryCast(textBoxControl.Template.FindName("PART_ContentHost", textBoxControl), FrameworkElement) If part IsNot Nothing Then Dim partPosition As Point = part.TransformToAncestor(textBoxControl).Transform(New Point(0, 0)) renderingOffset.X += partPosition.X renderingOffset.Y += partPosition.Y text.MaxTextWidth = Math.Max(part.ActualWidth - renderingOffset.X, 10) text.MaxTextHeight = Math.Max(part.ActualHeight, 10) End If ' Draw the text drawingContext.DrawText(text, renderingOffset) End Sub End Class
装飾は、Adorner クラスから継承します。 この特定の装飾は、プレースホルダー テキストを描画するために OnRender(DrawingContext) メソッドをオーバーライドします。 コードの詳細を見てみましょう。
- まず、
TextBoxHelper.GetPlaceholder(textBoxControl)
を呼び出してプレースホルダー テキストが存在することを確認します。 - FormattedText オブジェクトを作成します。 このオブジェクトには、ビジュアルに描画されるテキストに関するすべての情報が含まれています。
-
FormattedText.MaxTextWidthプロパティとFormattedText.MaxTextHeightプロパティの両方がコントロールの領域に設定されます。 また、
FormattedText
オブジェクトが有効であることを確認するために、最小値として 10 が設定されます。 -
renderingOffset
は、描画されたテキストの位置を格納します。 - コントロールのテンプレートで宣言されている場合は、
PART_ContentHost
を使用します。 この部分は、コントロールのテンプレートでテキストが描画される場所を表します。 そのパーツが見つかった場合は、その位置を考慮するようにrenderingOffset
を変更します。 -
DrawText(FormattedText, Point)を呼び出し、
FormattedText
オブジェクトとテキストの位置を渡してテキストを描画します。
- まず、
添付プロパティを適用する
添付プロパティが定義されたら、その名前空間を XAML にインポートし、 TextBox コントロールで参照する必要があります。 次のコードは、.NET 名前空間 DotnetDocsSample
を XML 名前空間 l
にマップします。
<Window x:Class="DotnetDocsSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:DotnetDocsSample"
Title="Recipe Tracker" Width="400" SizeToContent="Height">
<StackPanel Margin="10">
<TextBlock FontSize="20" TextWrapping="Wrap">Welcome to Recipe Tracker! To get started, create a new account.</TextBlock>
<Label Padding="0,5">Name</Label>
<TextBox l:TextBoxHelper.Placeholder="Ex. Jeffry Goh" />
<Label Padding="0,5">Email</Label>
<TextBox l:TextBoxHelper.Placeholder="jeffry@contoso.com" />
<Label Padding="0,5">Password</Label>
<PasswordBox />
<Button HorizontalAlignment="Right" Width="100" Margin="0,10,0,5">Submit</Button>
</StackPanel>
</Window>
添付プロパティは、構文TextBox
を使用してxmlNamespace:Class.Property
に追加されます。
完全な例
次のコードは、完全な TextBoxHelper
クラスです。
public static class TextBoxHelper
{
public static string GetPlaceholder(DependencyObject obj) =>
(string)obj.GetValue(PlaceholderProperty);
public static void SetPlaceholder(DependencyObject obj, string value) =>
obj.SetValue(PlaceholderProperty, value);
public static readonly DependencyProperty PlaceholderProperty =
DependencyProperty.RegisterAttached(
"Placeholder",
typeof(string),
typeof(TextBoxHelper),
new FrameworkPropertyMetadata(
defaultValue: null,
propertyChangedCallback: OnPlaceholderChanged)
);
private static void OnPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBox textBoxControl)
{
if (!textBoxControl.IsLoaded)
{
// Ensure that the events are not added multiple times
textBoxControl.Loaded -= TextBoxControl_Loaded;
textBoxControl.Loaded += TextBoxControl_Loaded;
}
textBoxControl.TextChanged -= TextBoxControl_TextChanged;
textBoxControl.TextChanged += TextBoxControl_TextChanged;
// If the adorner exists, invalidate it to draw the current text
if (GetOrCreateAdorner(textBoxControl, out PlaceholderAdorner adorner))
adorner.InvalidateVisual();
}
}
private static void TextBoxControl_Loaded(object sender, RoutedEventArgs e)
{
if (sender is TextBox textBoxControl)
{
textBoxControl.Loaded -= TextBoxControl_Loaded;
GetOrCreateAdorner(textBoxControl, out _);
}
}
private static void TextBoxControl_TextChanged(object sender, TextChangedEventArgs e)
{
if (sender is TextBox textBoxControl
&& GetOrCreateAdorner(textBoxControl, out PlaceholderAdorner adorner))
{
// Control has text. Hide the adorner.
if (textBoxControl.Text.Length > 0)
adorner.Visibility = Visibility.Hidden;
// Control has no text. Show the adorner.
else
adorner.Visibility = Visibility.Visible;
}
}
private static bool GetOrCreateAdorner(TextBox textBoxControl, out PlaceholderAdorner adorner)
{
// Get the adorner layer
AdornerLayer layer = AdornerLayer.GetAdornerLayer(textBoxControl);
// If null, it doesn't exist or the control's template isn't loaded
if (layer == null)
{
adorner = null;
return false;
}
// Layer exists, try to find the adorner
adorner = layer.GetAdorners(textBoxControl)?.OfType<PlaceholderAdorner>().FirstOrDefault();
// Adorner never added to control, so add it
if (adorner == null)
{
adorner = new PlaceholderAdorner(textBoxControl);
layer.Add(adorner);
}
return true;
}
public class PlaceholderAdorner : Adorner
{
public PlaceholderAdorner(TextBox textBox) : base(textBox) { }
protected override void OnRender(DrawingContext drawingContext)
{
TextBox textBoxControl = (TextBox)AdornedElement;
string placeholderValue = TextBoxHelper.GetPlaceholder(textBoxControl);
if (string.IsNullOrEmpty(placeholderValue))
return;
// Create the formatted text object
FormattedText text = new FormattedText(
placeholderValue,
System.Globalization.CultureInfo.CurrentCulture,
textBoxControl.FlowDirection,
new Typeface(textBoxControl.FontFamily,
textBoxControl.FontStyle,
textBoxControl.FontWeight,
textBoxControl.FontStretch),
textBoxControl.FontSize,
SystemColors.InactiveCaptionBrush,
VisualTreeHelper.GetDpi(textBoxControl).PixelsPerDip);
text.MaxTextWidth = System.Math.Max(textBoxControl.ActualWidth - textBoxControl.Padding.Left - textBoxControl.Padding.Right, 10);
text.MaxTextHeight = System.Math.Max(textBoxControl.ActualHeight, 10);
// Render based on padding of the control, to try and match where the textbox places text
Point renderingOffset = new Point(textBoxControl.Padding.Left, textBoxControl.Padding.Top);
// Template contains the content part; adjust sizes to try and align the text
if (textBoxControl.Template.FindName("PART_ContentHost", textBoxControl) is FrameworkElement part)
{
Point partPosition = part.TransformToAncestor(textBoxControl).Transform(new Point(0, 0));
renderingOffset.X += partPosition.X;
renderingOffset.Y += partPosition.Y;
text.MaxTextWidth = System.Math.Max(part.ActualWidth - renderingOffset.X, 10);
text.MaxTextHeight = System.Math.Max(part.ActualHeight, 10);
}
// Draw the text
drawingContext.DrawText(text, renderingOffset);
}
}
}
Public Class TextBoxHelper
Public Shared Function GetPlaceholder(obj As DependencyObject) As String
Return obj.GetValue(PlaceholderProperty)
End Function
Public Shared Sub SetPlaceholder(obj As DependencyObject, value As String)
obj.SetValue(PlaceholderProperty, value)
End Sub
Public Shared ReadOnly PlaceholderProperty As DependencyProperty =
DependencyProperty.RegisterAttached(
"Placeholder",
GetType(String),
GetType(TextBoxHelper),
New FrameworkPropertyMetadata(
defaultValue:=Nothing,
propertyChangedCallback:=AddressOf OnPlaceholderChanged)
)
Private Shared Sub OnPlaceholderChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim textBoxControl = TryCast(d, TextBox)
If textBoxControl IsNot Nothing Then
If Not textBoxControl.IsLoaded Then
'Ensure that the events are not added multiple times
RemoveHandler textBoxControl.Loaded, AddressOf TextBoxControl_Loaded
AddHandler textBoxControl.Loaded, AddressOf TextBoxControl_Loaded
End If
RemoveHandler textBoxControl.TextChanged, AddressOf TextBoxControl_TextChanged
AddHandler textBoxControl.TextChanged, AddressOf TextBoxControl_TextChanged
'If the adorner exists, invalidate it to draw the current text
Dim adorner As PlaceholderAdorner = Nothing
If GetOrCreateAdorner(textBoxControl, adorner) Then
adorner.InvalidateVisual()
End If
End If
End Sub
Private Shared Sub TextBoxControl_Loaded(sender As Object, e As RoutedEventArgs)
Dim textBoxControl As TextBox = TryCast(sender, TextBox)
If textBoxControl IsNot Nothing Then
RemoveHandler textBoxControl.Loaded, AddressOf TextBoxControl_Loaded
GetOrCreateAdorner(textBoxControl, Nothing)
End If
End Sub
Private Shared Sub TextBoxControl_TextChanged(sender As Object, e As TextChangedEventArgs)
Dim textBoxControl As TextBox = TryCast(sender, TextBox)
Dim adorner As PlaceholderAdorner = Nothing
If textBoxControl IsNot Nothing AndAlso GetOrCreateAdorner(textBoxControl, adorner) Then
If textBoxControl.Text.Length > 0 Then
'Control has text. Hide the adorner.
adorner.Visibility = Visibility.Hidden
Else
'Control has no text. Show the adorner.
adorner.Visibility = Visibility.Visible
End If
End If
End Sub
Private Shared Function GetOrCreateAdorner(textBoxControl As TextBox, ByRef adorner As PlaceholderAdorner) As Boolean
'Get the adorner layer
Dim layer As AdornerLayer = AdornerLayer.GetAdornerLayer(textBoxControl)
'If nothing, it doesn't exist or the control's template isn't loaded
If layer Is Nothing Then
adorner = Nothing
Return False
End If
'Layer exists, try to find the adorner
adorner = layer.GetAdorners(textBoxControl)?.OfType(Of PlaceholderAdorner)().FirstOrDefault()
'Adorner never added to control, so add it
If adorner Is Nothing Then
adorner = New PlaceholderAdorner(textBoxControl)
layer.Add(adorner)
End If
Return True
End Function
Public Class PlaceholderAdorner
Inherits Adorner
Public Sub New(adornedElement As UIElement)
MyBase.New(adornedElement)
End Sub
Protected Overrides Sub OnRender(drawingContext As DrawingContext)
Dim textBoxControl As TextBox = DirectCast(AdornedElement, TextBox)
Dim placeholderValue As String = TextBoxHelper.GetPlaceholder(textBoxControl)
If String.IsNullOrEmpty(placeholderValue) Then
Return
End If
'Create the formatted text object
Dim text As New FormattedText(
placeholderValue,
System.Globalization.CultureInfo.CurrentCulture,
textBoxControl.FlowDirection,
New Typeface(textBoxControl.FontFamily,
textBoxControl.FontStyle,
textBoxControl.FontWeight,
textBoxControl.FontStretch),
textBoxControl.FontSize,
SystemColors.InactiveCaptionBrush,
VisualTreeHelper.GetDpi(textBoxControl).PixelsPerDip)
text.MaxTextWidth = Math.Max(textBoxControl.ActualWidth - textBoxControl.Padding.Left - textBoxControl.Padding.Right, 10)
text.MaxTextHeight = Math.Max(textBoxControl.ActualHeight, 10)
'Render based on padding of the control, to try and match where the textbox places text
Dim renderingOffset As New Point(textBoxControl.Padding.Left, textBoxControl.Padding.Top)
'Template contains the content part; adjust sizes to try and align the text
Dim part As FrameworkElement = TryCast(textBoxControl.Template.FindName("PART_ContentHost", textBoxControl), FrameworkElement)
If part IsNot Nothing Then
Dim partPosition As Point = part.TransformToAncestor(textBoxControl).Transform(New Point(0, 0))
renderingOffset.X += partPosition.X
renderingOffset.Y += partPosition.Y
text.MaxTextWidth = Math.Max(part.ActualWidth - renderingOffset.X, 10)
text.MaxTextHeight = Math.Max(part.ActualHeight, 10)
End If
' Draw the text
drawingContext.DrawText(text, renderingOffset)
End Sub
End Class
End Class
こちらも参照ください
.NET Desktop feedback