최근에 프로젝트를 진행하면서 ListView Control을 하며 어려웠던 부분을 정리하였습니다.
👉 기본 환경
- Language: C#, xaml
- IDE: Visual Basic 2022
- Framework: .NET 8.0
Scroll 처리를 버튼으로 만든 것..
제가 구현했던 동작 중 난이도 최상..!
✍️ ListView에 이미지를 클릭할 때, 스크롤이 이동하면서 선택된 이미지가 중앙에 오는 동작입니다.
View
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
<Window>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<!--// Window //-->
<Grid Grid.Row="0">
</Grid>
<!--// Image //-->
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="100"/>
</Grid.RowDefinitions>
<!--// Selected Image //-->
<Image Grid.Row="0" Source="{Binding SelectedImageSource}"/>
<!--// Image List //-->
<ListView Name="listViewImageSources"
Grid.Row="1"
ItemsSource="{Binding ImageSources}"
SelectedValue="{Binding SelectedImageSource, Mode=TwoWay}"
SelectedIndex="{Binding SelectedImageSourceIndex, Mode=TwoWay}"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
>
<b:Interaction.Triggers>
<b:EventTrigger EventName="MouseLeftButtonUp">
<b:InvokeCommandAction Command="{Binding ScrollImageCommand}"
CommandParameter="{Binding ElementName=listViewImageSources}"
/>
</b:EventTrigger>
</b:Interaction.Triggers>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Width" Value="75"/>
<Setter Property="Height" Value="75"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding .}" Stretch="Fill"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<!--// Etc.. //-->
<Grid Grid.Row="2">
</Grid>
</Grid>
</Window>
|
ListView
* SelectedValue와 SelectedIndex를 모두 설정한 이유는
* SelectedValue: Image Source를 바인딩하기 위함이고,
* SelectedIndex: Scroll 위치 계산에 사용하기 위함입니다.
* ScrollViewer는 Hidden 처리하고 동작을 Select 이벤트에 넘겼습니다.
* InvokeCommand: Nuget Behaviors 설치했습니다.
* 보통 Click 이벤트 구현은 xaml.cs로 넘어가게 되는데, MVVM 패턴을 유지하기 위함입니다.
ViewModel
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
namespace ScrollButton.ViewModels
{
public class ViewModelMain : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Properties
private ImageSource _selectedImageSource;
public ImageSource SelectedImageSource
{
get { return _selectedImageSource; }
set
{ _selectedImageSource = value; OnPropertyChange(nameof(SelectedImageSource)); } }
private int _SelectedImageSourceIndex;
public int SelectedImageSourceIndex
{
get { return _SelectedImageSourceIndex; }
set { _SelectedImageSourceIndex = value; }
}
private ObservableCollection<ImageSource> _imageSources = new ObservableCollection<ImageSource>();
public ObservableCollection<ImageSource> ImageSources
{
get { return _imageSources; }
set { _imageSources = value; }
}
#endregion
#region Commands
public RelayCommand ScrollImageCommand => null ?? new RelayCommand(ScrollImageEvent);
#endregion
#region Constructors
public ViewModelMain()
{
ImageSources = new ObservableCollection<ImageSource>
{
new BitmapImage(new Uri("pack://application:,,,/Resources/number0.png"))
, new BitmapImage(new Uri("pack://application:,,,/Resources/number1.png"))
, new BitmapImage(new Uri("pack://application:,,,/Resources/number2.png"))
, new BitmapImage(new Uri("pack://application:,,,/Resources/number3.png"))
, new BitmapImage(new Uri("pack://application:,,,/Resources/number4.png"))
, new BitmapImage(new Uri("pack://application:,,,/Resources/number5.png"))
, new BitmapImage(new Uri("pack://application:,,,/Resources/number6.png"))
, new BitmapImage(new Uri("pack://application:,,,/Resources/number7.png"))
, new BitmapImage(new Uri("pack://application:,,,/Resources/number8.png"))
, new BitmapImage(new Uri("pack://application:,,,/Resources/number9.png"))
};
SelectedImageSource = ImageSources[0];
}
#endregion
#region Methods
private void ScrollImageEvent(object obj)
{
if (obj is ListView listView)
{
var scrollViewer = FindScrollViewer(listView);
if (scrollViewer != null)
{
int middlePositionIndex = 2; // 중앙에 위치시키고자 하는 인덱스
double newHorizontalOffset = SelectedImageSourceIndex - middlePositionIndex;
scrollViewer.ScrollToHorizontalOffset(newHorizontalOffset);
scrollViewer.UpdateLayout();
}
}
}
private ScrollViewer FindScrollViewer(DependencyObject dependencyObject)
{
if (dependencyObject is ScrollViewer)
{
return dependencyObject as ScrollViewer;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
var scrollViewerInListView = FindScrollViewer(child);
if (scrollViewerInListView != null)
{
return scrollViewerInListView;
}
}
return null;
}
#endregion
}
}
|
|
|
🚨 문제가 ScrollViewer를 어떻게 얻어오냐..였습니다.
View와 ViewModel이 독립적이어야한다고 했지만, 도저히 방도가 생각나지 않아 CommandParameter로 ListView를 넘기고 ListView 하위에 있는 ScrollViewer를 찾아서 반환하는 형식으로 진행했습니다.
😮 오늘의 깨달음: Web의 Scroll Event Control과 프로그램의 Scroll Event Control은 다르다..!
'C# > WPF' 카테고리의 다른 글
[WPF] WebView2와 동영상 (0) | 2024.04.13 |
---|---|
[WPF] Button 클릭 영역과 Background (0) | 2024.04.06 |
[WPF] ListView와 SelectedItem 초기화 (0) | 2024.03.31 |
[WPF] Converter와 Visibility (0) | 2024.03.18 |
[WPF] ResourceDictionary (0) | 2024.03.18 |