티스토리 뷰
2022.09.06 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내v1.0 part9-2 StyleSelector
2022.08.31 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part9-1 DataTemplateSelector
2022.08.08 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part8-3 Template
2022.08.02 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part8-2 Template
2022.07.21 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part8-1 Template
2022.07.13 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part7 Behavior
2022.07.05 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part6 Command
2022.06.27 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part5 Converter
2022.06.15 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part4-2 Data Binding
2022.06.13 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part4-1 Data Binding
2022.06.07 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part3-3
2022.06.02 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part3-2
2022.05.30 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part3-1
2022.05.11 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part2
2022.05.09 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part1
Behavior
1. 특징
- 컨트롤에 연결해서 사용해야 합니다.
- 컨트롤을 수정하지 않고 컨트롤에 추가 기능을 구현 할 수 있습니다.
- OnAttached(), OnDetaching() 메서드를 이용해서, 컨트롤에 연결될 때와 분리될 때의 동작을 정의할 수 있습니다.
- AssociatedObject 프로퍼티를 이용해서 연결된 컨트롤에 접근 할 수 있습니다.
- 컨트롤의 이벤트 핸들러를 이용해서 다양한 기능을 구현하고 사용할 수 있습니다.
- 다른 화면이나 프로젝트에서도 사용할 수 있어 재사용성이 높습니다.
- 대부분 XAML에서 사용되며, StaticResource로 만들 수 없습니다.
2. 왜 Behavior를 만들어서 사용해야하죠?
- 기본 TextBox는 포커스를 받을 때 입력된 모든 택스트를 선택하는 기능이 없습니다.
- 기능을 구현하기 위해서는 TextBox의 GotFocus 이벤트 핸들러를 추가하고, Dispatcher와 컨트롤의 SelectAll() 메서드를 이용해야 합니다.
- 이런 기능들은 ViewModel에서 제어하기가 어렵고, 논리적 영역에서 컨트롤의 직접적인 제어를 하지 말아야 합니다. 그래서, Behavior에서 그런 기능을 담당하게 됩니다.
- 모든 TextBox 컨트롤에 이 기능을 구현하려면 어떻게 하는 것이 좋을까요?
- TextBox 컨트롤을 상속 받는 TextBoxEx라는 컨트롤을 만드는 방법
- 컨트롤 상속을 이용해서 컨트롤을 생성하는 것은 복잡한 동작이 필요하거나, protected에 접근해야 하는 경우에만 제한적으로 사용하는 것이 권장됩니다.
- Behavior를 만들어서 각 TextBox에 붙이는 방법
- TextBox 컨트롤을 상속 받는 TextBoxEx라는 컨트롤을 만드는 방법
3rd party 상용 컨트롤을 이용하는 방법도 있습니다.~
3. Behavior 만들기
- Microsoft.Xaml.Behaviors.Wpf nuget package를 추가합니다.
- 클래스를 만듭니다.
- Behavior<Control>을 상속 받습니다. Control은 Behavior가 사용하려는 컨트롤을 입력하시면 됩니다.
- TextBox, CheckBox, ComboBox 등
- OnAttached()와 OnDetaching() 메서드를 override 합니다.
- OnAttached() 메서드에서 AssociatedObject 프로퍼티를 이용해서 사용하려는 이벤트 핸들러를 추가합니다.
- OnDetaching() 메서드에서 연결했던 이벤트 핸들러를 제거합니다.
- Dependency Property를 추가해서 Binding을 할 수도 있습니다.
- 작성 완료 후 XAML에서 컨트롤에 붙여서 사용합니다.
TextBoxBehavior.cs
public class TextBoxBehavior : Behavior<TextBox>
{
/// <summary>
/// SelectAll 사용 여부 기본은 true
/// </summary>
public bool IsSelectAll { get; set; } = true;
protected override void OnAttached()
{
//AssoicatedObject의 이벤트 핸들러 추가 - 삭제는 반드시 쌍으로 처리합니다.
if (IsSelectAll)
{
AssociatedObject.GotFocus += AssociatedObject_GotFocus;
}
AssociatedObject.KeyDown += AssociatedObject_KeyDown;
}
private void AssociatedObject_KeyDown(object sender, KeyEventArgs e)
{
//Enter Key입력시 EnterCommand 실행 이벤트 아규먼트 넘기는 것은 옵션
if (e.Key == Key.Enter && EnterCommand != null)
{
EnterCommand.Execute(e);
e.Handled = true;
}
}
private void AssociatedObject_GotFocus(object sender, System.Windows.RoutedEventArgs e)
{
//Dispatcher를 이용해야 기능이 정상 동작합니다.
Dispatcher.BeginInvoke(
() =>
{
AssociatedObject.SelectAll();
}, null);
}
protected override void OnDetaching()
{
if (IsSelectAll)
{
AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
}
AssociatedObject.KeyDown -= AssociatedObject_KeyDown;
}
public ICommand EnterCommand
{
get { return (ICommand)GetValue(EnterCommandProperty); }
set { SetValue(EnterCommandProperty, value); }
}
/// <summary>
/// EnterCommand
/// </summary>
public static readonly DependencyProperty EnterCommandProperty =
DependencyProperty.Register(nameof(EnterCommand), typeof(ICommand), typeof(TextBoxBehavior), new PropertyMetadata(null));
}
4. Behavior 사용하기
b 네임스페이스와 Behavior의 네임스페이스를 선언합니다.
Behavior를 붙이려는 컨트롤 내부에 아래와 같은 형태로 추가하시면 됩니다.
<Window
x:Class="BehaviorSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BehaviorSample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<TextBox Text="{Binding Name}">
<b:Interaction.Behaviors>
<local:TextBoxBehavior EnterCommand="{Binding EnterCommand}"/>
</b:Interaction.Behaviors>
</TextBox>
포커스시 전체 선택
엔터키를 입력했을 때
5. Behavior 만들 때 주의 사항
- 디자인 타임에서도 생성되고 실행되기 때문에, 런타임에만 데이터가 등록되는 프로퍼티나 메서드의 사용시 주의 필요
- 특히 Dependency Property를 사용하고, PropertyChanged 이벤트 핸들러를 추가한 경우 핸들러에서도 null 체크를 잘 해주어야 함
- Loaded 이벤트를 사용하는 경우에도 디자인 타임에서 에러가 발생하지 않도록 주의해야 합니다.
- DesignerProperties.GetIsInDesignMode(new DependencyObject()) 문구를 이용하면, 디자인 타임 여부를 확인할 수 있습니다.
아래와 같이 EnterCommandChanged 이벤트 핸들러를 추가해서 사용한다고 했을 때...
/// <summary>
/// EnterCommand
/// </summary>
public static readonly DependencyProperty EnterCommandProperty =
DependencyProperty.Register(nameof(EnterCommand), typeof(ICommand),
typeof(TextBoxBehavior), new PropertyMetadata(null, EnterCommandChanged));
private static void EnterCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = (TextBoxBehavior)d;
behavior.AssociatedObject.Text = "EnterCommandChanged";
}
디자인 타임에 아래와 같은 오류가 발생합니다.
여기서 오류가 발생한 원인은 AssociatedObject가 null이기 때문입니다.
6. TextBox를 1000개도 넘게 사용하는데 일일이 추가해야 하나요?
- Blend Behaviors in Styles
- Style에서 Behavior 사용 여부를 지정할 수 있는 클래스입니다.
- 사용법
-
public class TextBoxExBehavior : AttachableForStyleBehavior<TextBox, TextBoxExBehavior>
- Behavior를 생성할 때 해당 클래스를 이용합니다.
- 스타일을 만들고 아래와 같이 IsEnabledForStyle 속성을 true로 설정하고, 컨트롤에 스타일을 적용하면 Behavior가 자동으로 추가됩니다.
-
AttachableForStyleBehavior.cs
/// <summary>
/// 스타일에서 Behavior을 추가해서 사용할 수 있도록 지원합니다.
/// </summary>
/// <remarks>https://stackoverflow.com/questions/1647815/how-to-add-a-blend-behavior-in-a-style-setter</remarks>
/// <typeparam name="TComponent"></typeparam>
/// <typeparam name="TBehavior"></typeparam>
public class AttachableForStyleBehavior<TComponent, TBehavior> : Behavior<TComponent>
where TComponent : DependencyObject
where TBehavior : AttachableForStyleBehavior<TComponent, TBehavior>, new()
{
#region IsEnabledForStyle
public static DependencyProperty IsEnabledForStyleProperty =
DependencyProperty.RegisterAttached(nameof(IsEnabledForStyle), typeof(bool),
typeof(AttachableForStyleBehavior<TComponent, TBehavior>), new FrameworkPropertyMetadata(false, OnIsEnabledForStyleChanged));
public bool IsEnabledForStyle
{
get => (bool)GetValue(IsEnabledForStyleProperty);
set => SetValue(IsEnabledForStyleProperty, value);
}
private static void OnIsEnabledForStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement uie = d as UIElement;
if (uie != null)
{
BehaviorCollection behColl = Interaction.GetBehaviors(uie);
TBehavior existingBehavior = behColl.FirstOrDefault(b => b.GetType() ==
typeof(TBehavior)) as TBehavior;
if ((bool)e.NewValue == false && existingBehavior != null)
{
behColl.Remove(existingBehavior);
}
else if ((bool)e.NewValue == true && existingBehavior == null)
{
behColl.Add(new TBehavior());
}
}
}
#endregion IsEnabledForStyle
}
TextBoxExBehavior.cs
public class TextBoxExBehavior : AttachableForStyleBehavior<TextBox, TextBoxExBehavior>
{
private readonly IDynamicResource _dr;
public TextBoxExBehavior()
{
Debug.WriteLine("Create TextBoxExBehavior");
if(App.IsInDesignMode())
{
return;
}
_dr = App.Current.Services.GetService(typeof(IDynamicResource)) as IDynamicResource;
}
protected override void OnAttached()
{
AssociatedObject.GotFocus += AssociatedObject_GotFocus;
if (_dr == null) return;
//OnAttached가 UI Thread에서 실행되지 않기 때문에 Dispatcher 이용해서 출력
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
() =>
{
var text = _dr["wrd_Hello"];
AssociatedObject.Text = text;
});
}
private void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
{
Debug.WriteLine("AssociatedObject_GotFocus in TextBoxExBehavior");
}
protected override void OnDetaching()
{
AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
}
}
스타일 만들기
x:Key를 입력하지 않으면 모든 TextBox에 적용됩니다~
<Style x:Key="AttachedTextBoxExStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="20" />
<Setter Property="local:TextBoxExBehavior.IsEnabledForStyle" Value="True" />
</Style>
스타일 적용
<TextBox Style="{StaticResource AttachedTextBoxExStyle}" Text="{Binding Address}" />
실행 화면
7. 마무리
Behavior에 대한 문의 사항은 언제나 환영합니다~
WpfTest/BehaviorSample at master · kaki104/WpfTest (github.com)
'WPF .NET' 카테고리의 다른 글
MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part8-2 Template (2) | 2022.08.02 |
---|---|
MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part8-1 Template (0) | 2022.07.21 |
MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part6 Command (0) | 2022.07.05 |
MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part5 Converter (0) | 2022.06.27 |
MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part4-2 Data Binding (0) | 2022.06.15 |
- Total
- Today
- Yesterday
- PRISM
- XAML
- UWP
- Windows 10
- Cross-platform
- windows 11
- Bot Framework
- ComboBox
- MVVM
- #Windows Template Studio
- Build 2016
- uno-platform
- IOT
- WPF
- Behavior
- dotNETconf
- #prism
- #uwp
- LINQ
- Always Encrypted
- Visual Studio 2022
- .net
- ef core
- visual studio 2019
- .net 5.0
- uno platform
- C#
- Microsoft
- kiosk
- #MVVM
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |