editable 리스트박스? 리스트뷰?

지금 개발중인 프로젝트에서 문자열 리스트가 있고

그걸 ListView 혹은 ListBox로 표시 하려고 합니다.

거기에 셀을 더블클릭 하면 에디팅이 되게 하려고 하는데

이게 생각보다 구현이 어렵네요.

보통 어떤식으로 구현 하시나요?

데이터그리드 말곤 답이 없을까요?

구현 예시

ezgif.com-video-to-webp-converter

View

ListBoxItemTemplate 속성을 재정의 해서 DataTemplate내에서 TextBlock 대신
EditableTextBlock이라는 사용자 정의 컨트롤을 통해 데이터를 표출하도록 합니다.

<ListBox Width="200"
            Height="100"
            ItemsSource="{Binding Items}">
    <ListBox.ItemContainerStyle>
        <Style BasedOn="{StaticResource {x:Type ListBoxItem}}" TargetType="{x:Type ListBoxItem}">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        </Style>
    </ListBox.ItemContainerStyle>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <local:EditableTextBlock Text="{Binding Name}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox> 

이러한 방식으로 구현할 경우 ListViewGridView에서도 CellTemplate 속성으로 지정하여 적용 가능합니다.

ViewModel

여기서 주의할 점은 ListBoxItemDataContextstring 자체로 설정될 경우 Two-way Binding이 동작하지 않으므로 List<string>와 같이 string 값을 직접 사용할 수 없고 어떤 형식(SomeType) 내의 속성(Name)으로 노출해야 한다는 것입니다.

public class ViewModel
{
    public ObservableCollection<SomeType> Items { get; } = 
        [new("Item1"), new("Item2"), new("Item3")];
}

public class SomeType
{
	public SomeType(string name) => Name = name;

	public string Name { get; set; }
}

EditableTextBlock 컨트롤

EditableTextBlock 스타일

<Style TargetType="{x:Type local:EditableTextBlock}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:EditableTextBlock}">
                <Grid x:Name="PART_GridContainer"
                        Background="Transparent">
                    <TextBlock x:Name="PART_DisplayText"
                                Visibility="Visible"
                                Text="{TemplateBinding Text}" />

                    <TextBox x:Name="PART_EditText"
                                Padding="-2,0,0,0"
                                Visibility="Collapsed"
                                Background="Transparent"
                                BorderThickness="0"
                                Text="{Binding Text,
                                            Mode=TwoWay,
                                            RelativeSource={RelativeSource TemplatedParent}}" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

EditableTextBlock 코드 비하인드

[TemplatePart(Type = typeof(Grid), Name = PART_GRID)]
[TemplatePart(Type = typeof(TextBlock), Name = PART_DISPLAYTEXT)]
[TemplatePart(Type = typeof(TextBox), Name = PART_EDITTEXT)]
public class EditableTextBlock : Control
{
	private const string PART_GRID = "PART_GridContainer";
	private const string PART_DISPLAYTEXT = "PART_DisplayText";
	private const string PART_EDITTEXT = "PART_EditText";

	private Grid _gridContainer;
	private TextBlock _textBlockDisplayText;
	private TextBox _textBoxEditText;

	public static readonly DependencyProperty TextProperty =
		DependencyProperty.Register(
			"Text",
			typeof(string),
			typeof(EditableTextBlock),
			new UIPropertyMetadata(string.Empty));

	static EditableTextBlock()
	{
		DefaultStyleKeyProperty.OverrideMetadata(
			typeof(EditableTextBlock),
			new FrameworkPropertyMetadata(
				typeof(EditableTextBlock),
				FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
	}

	public string Text
	{
		get => (string)GetValue(TextProperty);
		set => SetValue(TextProperty, value);
	}

	private void OnTextBoxLostFocus(object sender, RoutedEventArgs e)
	{
		_textBlockDisplayText.Visibility = Visibility.Visible;
		_textBoxEditText.Visibility = Visibility.Collapsed;
	}

	protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
	{
		base.OnMouseDoubleClick(e);

		_textBlockDisplayText.Visibility = Visibility.Collapsed;
		_textBoxEditText.Visibility = Visibility.Visible;

		_textBoxEditText.Focus();
		_textBoxEditText.SelectAll();

		e.Handled = true;
	}

	public override void OnApplyTemplate()
	{
		base.OnApplyTemplate();

		_gridContainer = Template.FindName(PART_GRID, this) as Grid;
		_textBoxEditText = Template.FindName(PART_EDITTEXT, this) as TextBox;
		_textBlockDisplayText = Template.FindName(PART_DISPLAYTEXT, this) as TextBlock;

		if (_textBoxEditText != null)
		{
			_textBoxEditText.LostFocus += OnTextBoxLostFocus;
		}
	}
}

만약 ESC 키를 누를 경우 편집한 속성 값이 복원되기를 원하신다면 EndEdit, CancelEdit 등과 같은 패턴을 추가로 구현하시면 됩니다.

도움이 되셨으면 합니다. 이상입니다.

ListBox with EditableTextBlock.docx (17.5 KB)

9 Likes

감사합니다!