지금 개발중인 프로젝트에서 문자열 리스트가 있고
그걸 ListView 혹은 ListBox로 표시 하려고 합니다.
거기에 셀을 더블클릭 하면 에디팅이 되게 하려고 하는데
이게 생각보다 구현이 어렵네요.
보통 어떤식으로 구현 하시나요?
데이터그리드 말곤 답이 없을까요?
지금 개발중인 프로젝트에서 문자열 리스트가 있고
그걸 ListView 혹은 ListBox로 표시 하려고 합니다.
거기에 셀을 더블클릭 하면 에디팅이 되게 하려고 하는데
이게 생각보다 구현이 어렵네요.
보통 어떤식으로 구현 하시나요?
데이터그리드 말곤 답이 없을까요?
ListBox
의 ItemTemplate
속성을 재정의 해서 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>
이러한 방식으로 구현할 경우 ListView
의 GridView
에서도 CellTemplate
속성으로 지정하여 적용 가능합니다.
여기서 주의할 점은 ListBoxItem
의 DataContext
가 string
자체로 설정될 경우 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)
감사합니다!