이번에 다양한 버튼 클릭시 어떤 Textbox로 포커스를 가게하는 요구사항이 들어와 프로그램을 수정하려고 합니다.
각 버튼마다 Focus를 주는 방법도 있겠지만 MVVM방식으로 만든 프로그램이기도하고, 다양한 버튼에서 해당 포커스를 요구하기 때문에 각 버튼에 주면 코드가 더러워질 것 같아 MVVM방식으로 찾아봤습니다.
저는 위 방법을 통해 포커스를 주었는데, 혹시 다른분들은 어떻게 포커스를 주시는지 궁금하네요!
혹시 저랑 같은 고민을 하셨던 분이라면 저 유튜브가 도움이 될 것 같기도합니다!
4개의 좋아요
저같은 경우는 재사용을 위해서 Behavior를 사용합니다.
Behavior로 만들어놓으면
<behavior:Interaction.Behaviors>
<local:FocusBehavior IsControlFocus="{Binding DataContext.IsUserNameFocus, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"/>
</behavior:Interaction.Behaviors>
<behavior:Interaction.Behaviors>
<local:FocusBehavior IsControlFocus="{Binding DataContext.IsPasswordFocus, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"/>
</behavior:Interaction.Behaviors>
해당 부분만 복붙이 가능해서 편합니다.
FocusBehavior.cs
using Microsoft.Xaml.Behaviors;
using System.Windows;
using System.Windows.Controls.Primitives;
namespace WpfApp1
{
public class FocusBehavior : Behavior<TextBoxBase>
{
public static readonly DependencyProperty IsControlFocusProperty =
DependencyProperty.Register(nameof(IsControlFocus), typeof(bool), typeof(FocusBehavior), new PropertyMetadata(false, propertyChangedCallback: IsControlFocusPropertyChanged));
public bool IsControlFocus
{
get { return (bool)GetValue(IsControlFocusProperty); }
set { SetValue(IsControlFocusProperty, value); }
}
private static void IsControlFocusPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FocusBehavior behavior)
{
behavior.AssociatedObject.Focus();
behavior.AssociatedObject.SelectAll();
}
}
}
}
MainWindow.xaml
<Window 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:WpfApp1"
xmlns:behavior="http://schemas.microsoft.com/xaml/behaviors"
x:Class="WpfApp1.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="450">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<StackPanel VerticalAlignment="Center">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel.Resources>
<Style TargetType="TextBox">
<Setter Property="Width" Value="100"/>
<Setter Property="Margin" Value="0,5"/>
</Style>
</StackPanel.Resources>
<TextBlock Text="UserName"/>
<TextBox>
<behavior:Interaction.Behaviors>
<local:FocusBehavior IsControlFocus="{Binding DataContext.IsUserNameFocus, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"/>
</behavior:Interaction.Behaviors>
</TextBox>
<TextBlock Text="Password"/>
<TextBox>
<behavior:Interaction.Behaviors>
<local:FocusBehavior IsControlFocus="{Binding DataContext.IsPasswordFocus, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"/>
</behavior:Interaction.Behaviors>
</TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="Width" Value="120"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Margin" Value="5,0"/>
</Style>
</StackPanel.Resources>
<Button Content="UserName Focus"
Command="{Binding UserNameFocusCommand}"/>
<Button Content="Password Focus"
Command="{Binding PasswordFocusCommand}"/>
</StackPanel>
</StackPanel>
</Window>
MainWindowViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace WpfApp1
{
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private bool _isUserNameFocus;
[ObservableProperty]
private bool _isPasswordFocus;
public MainWindowViewModel()
{
}
[RelayCommand]
private void UserNameFocus()
{
IsUserNameFocus = !IsUserNameFocus;
}
[RelayCommand]
private void PasswordFocus()
{
IsPasswordFocus = !IsPasswordFocus;
}
}
}
10개의 좋아요
루나시아
3
포커스를 가져와야 하는 속성이 여러 개라면 동적으로 처리하는 게 편하지 않을까 싶어서 짜봤습니다.
- 포커스가 필요한 속성의 이름을 dummy TextBox에 바인딩
- dummy TextBox의 TextChanged 이벤트를 통해 포커스가 필요한 속성의 이름을 가져옴
- 자식 컨트롤 중에 포커스가 필요한 속성과 바인딩된 Dependency Property 또는 Attached Property가 있는 컨트롤이 있는지 확인 후 해당 컨트롤을 가져옴
- 해당 컨트롤이 FrameworkElement인지 확인 후, 맞으면 포커스 이동
단점
- 컨트롤을 가져오기 위해 논리적 트리를 순회하기 때문에 직접 가져오는 방법에 비해서 속도가 떨어질 수 있다.
- 뷰모델의 속성과 DP or AP간 일대일 바인딩일 때만 정상적으로 동작하며, 바인딩된 DP or AP가 여러 개일 때는 논리적 트리에서 가장 먼저 발견되는 컨트롤로 포커스가 이동한다.
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
// UserName, Password 등 생략 //
public string? FocusTargetPropertyName
{
get => _focusTargetPropertyName;
set
{
_focusTargetPropertyName = value;
OnPropertyChanged();
}
}
// Command 구현부 생략 //
private void Login()
{
if (string.IsNullOrEmpty(UserName))
{
Message = "사용자 이름을 입력하세요";
FocusTargetPropertyName = nameof(UserName);
return;
}
else if (string.IsNullOrEmpty(Password))
{
Message = "패스워드를 입력하세요.";
FocusTargetPropertyName = nameof(Password);
return;
}
Message = "로그인 성공!";
}
}
MainWindow.xaml
<Window x:Class="SetFocusInMvvm.MainWindow"
<!-- xml 네임스페이스 생략 -->
xmlns:local="clr-namespace:SetFocusInMvvm"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel Width="500"
Margin="10">
<StackPanel Margin="5">
<Label Content="User Name"/>
<TextBox Text="{Binding UserName}"/>
</StackPanel>
<StackPanel Margin="5">
<Label Content="Password"/>
<TextBox Text="{Binding Password}"/>
</StackPanel>
<Button Content="Login"
Width="100"
Margin="5 10"
Command="{Binding LoginCommand}"/>
<TextBlock Text="{Binding Message}"
HorizontalAlignment="Center"
FontSize="12"
Foreground="Red"/>
<TextBox x:Name="xFocusHandler"
Visibility="Hidden"
Text="{Binding FocusTargetPropertyName}"
TextChanged="FocusTargetPropertyChanged"/>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void FocusTargetPropertyChanged(object sender, TextChangedEventArgs e)
{
if (string.IsNullOrEmpty(xFocusHandler.Text))
{
e.Handled = true;
return;
}
string propertyName = xFocusHandler.Text;
// 대상 프로퍼티와 바인딩된 컨트롤을 논리적 트리에서 가져오는 부분
// (DependencyObjectExtension.cs)
var obj = this.FindBindingTarget(propertyName);
if (obj is null)
{
return;
}
if (obj is FrameworkElement fe)
{
fe.Focus();
}
xFocusHandler.SetCurrentValue(TextBox.TextProperty, string.Empty);
}
}
소스코드:
5개의 좋아요
@Nobody @루나시아 다양한 방법 감사합니다.
직접 사용해보며 좋은 코드를 볼 수 있는 좋은 기회였습니다!
4개의 좋아요