최근에 프로젝트를 진행하면서 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(thisnew 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

최근에 프로젝트를 진행하면서 ListView Control을 하며 어려웠던 부분을 정리하였습니다.

 

👉 기본 환경

- Language: C#, xaml

- IDE: Visual Basic 2022

- Framework: .NET 8.0


사내 프로젝트도 어느덧 막바지를 향해 가고 있습니다.

 

설계팀으로부터 테스트 결과를 받았습니다.

Click 이벤트 컨트롤이 잘 되지 않아 해결을 하고, 그 해결은 또 다른 문제를 불러오게 되는데..

의 굴레에서 빠져나온 이야기를 짧게 정리해보고자 합니다✍️.

 

상황:

  1. ListView에서 Item을 클릭했을 때, 데이터를 로드하고,

  2. DataContext로 연결한 ViewModel에 로드된 데이터를 넘겨주면서

  3. Window를 여는 것이었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
private void ClickItemEvent()
{
    // 생략
    
    if(SelectedItem != null && SelectedItem.Title != null)
    {
        LoadDataModel(SelectedItem);
        
        WindowDetails windowDetails = new WindowDetails();
        windowDetails.DataContext = new ViewModelDetails(SelectedDataModel);
        windowDetails.ShowDialog();
    }
}

 

문제:

기존에 ClickItemEvent가 1번이라도 발생해서 SelectedItem이 설정되면,

더블클릭을 통한 창 최대/최소 시에 ListView가 클릭된 것처럼 windowDetails 창이 열렸습니다.

 

SelectedItem이 null값일 때, window가 열리지 않도록 유효성 검사를 조건으로도 해결할 수 없는 문제였기에 다른 해결책이 필요했습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void ClickAddinItemEvent()
{
    // 생략
    
    if(SelectedItem != null && SelectedItem.Title != null)
    {
        LoadDataModel(SelectedItem);
        
        WindowDetails windowDetails = new WindowDetails();
        windowDetails.DataContext = new ViewModelDetails(SelectedDataModel);
        windowDetails.ShowDialog();
 
        // Initialize Data
        SelectedItem = new ItemModel();
    }
}

 

해결1:

새로운 ItemModel()로 초기화

→ 부정확한 click 이벤트에 대해서는 windowDetails가 열리지 않음

🚨 그러나, 같은 SelectedItem을 클릭했을 때,  windowDetails가 열리지 않음

 

🤓 이유:

ListView의 총 3개의 ListViewItem이 있다고 가정했을 때,

  * ItemA

  * ItemB

  * ItemC

ItemA에 대한 SelectedItem의 참조값이 0001 일 때, 새로운 인스턴스로 초기화하며 종료했으므로 0002로 변경

  * ItemA: 0001 → 0002

  * ItemB: 0003

  * ItemC: 0004

 

새로운 Item인 B나 C를 선택했을 때는 참조값이 변경되면서 변화를 인식하지만,

동일한 Item인 A를 클릭했을 때는 이미 빈 참조값인 0002로 초기화되었기 때문에 변화를 인식하지 못함

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void ClickAddinItemEvent()
{
    // 생략
    
    if(SelectedItem != null && SelectedItem.Title != null)
    {
        LoadDataModel(SelectedItem);
        
        WindowDetails windowDetails = new WindowDetails();
        windowDetails.DataContext = new ViewModelDetails(SelectedDataModel);
        windowDetails.ShowDialog();
 
        // Initialize Data
        SelectedItem = null;
    }
}

해결2:

null로 초기화

→ 참조값이 없어지므로 동일한 Item을 클릭해도 SelectedItem의 변화가 인식됨

 

 

 

✍️ 값 초기화를 null로 하지 않기 위해 새로운 인스턴스를 사용헀는데, 오히려 동작인식이 되지 않아 새로운 문제가 발생했었습니다. 이에 대해 오히려 WPF의 동작원리를 조금 더 깊게 알게 된 것 같아 뿌듯했습니다🤗.

 

'C# > WPF' 카테고리의 다른 글

[WPF] Button 클릭 영역과 Background  (0) 2024.04.06
[WPF] MVVM Scroll 동작 구현  (0) 2024.03.31
[WPF] Converter와 Visibility  (0) 2024.03.18
[WPF] ResourceDictionary  (0) 2024.03.18
[WPF] Popup Control  (1) 2024.03.17

최근에 프로젝트를 진행하면서 ListView Control을 하며 어려웠던 부분을 정리하였습니다.

 

👉 기본 환경

- Language: C#, xaml

- IDE: Visual Basic 2022


bool 타입의 값으로 Visibility를 컨트롤 하는 일이 많았습니다.

 

이번 기회에 사용한 Converter를 정리🗂️해 봅니다.

 

BooleanToVisibleConverter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BooleanToVisibleConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        bool val = (bool)value;
        if (val)
        {
            return System.Windows.Visibility.Visible;
        }
        else
        {
            return System.Windows.Visibility.Collapsed;
        }
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

 

NegativeBooleanConverter

1
2
3
4
5
6
7
8
9
10
11
public class NegativeBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return !((bool)value);
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

 

ChainConverter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ChainConverter : IValueConverter
{
    public IValueConverter Converter1 { get; set; }
    public IValueConverter Converter2 { get; set; }
 
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        object convertedValue = Converter1.Convert(value, targetType, parameter, culture);
        return Converter2.Convert(convertedValue, targetType, parameter, culture);
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

 

Window / UserControl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<Window>
    <Window.Resources>
        <ResourceDictionary>
            <converter:BooleanToVisibleConveter x:Key="boolVisibleInverter"/>
            <converter:NegativeBooleanConverter x:Key="negativeBoolInverter"/>
            <converter:ChainConverter x:Key="negativeBoolVisibleInverter"
                                      Converter1="{StaticResource negativeBoolInverter}"
                                      Converter2="{StaticResource boolVisibleInverter}" />
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <TextBlock Text="If the value is true, this TextBlock will be Visible"
                       Visibility="{Binding TrueValue, Converter={StaticResource boolVisibleInverter}}" />
            <TextBlock Text="If the value is false, this TextBlock will be Visible"
                       Visibility="{Binding FalseValue, Converter={StaticResource negativeBoolVisibleInverter}}" />
        </StackPanel>
        <Grid Background="#000000" Opacity="0.5" 
              Visibility="{Binding TrueValue, Converter={StaticResource boolVisibleInverter}}">
        </Grid>
    </Grid>
</Window>

 

⭐ Converter와 Visibility를 통해서 Dim 처리를 구현할 수 있음

    * Dim 처리: 웹페이지에서 팝업을 띄울때 배경으로부터 팝업화면을 강조하기 위해 넣는 반투명 음영 영역

    (여기서는 18행의 Grid가 Dim 역할을 수행)

 

 

 

📚 참고 자료

 

WPF: how to use 2 converters in 1 binding?

I have a control that I want to show/hide, depending on the value of a boolean. I have a NegatedBooleanConverter (switches true to false and vice versa) and I need to run this converter first. I h...

stackoverflow.com

 

'C# > WPF' 카테고리의 다른 글

[WPF] MVVM Scroll 동작 구현  (0) 2024.03.31
[WPF] ListView와 SelectedItem 초기화  (0) 2024.03.31
[WPF] ResourceDictionary  (0) 2024.03.18
[WPF] Popup Control  (1) 2024.03.17
[WPF] 검색과 정렬  (0) 2024.03.10

최근에 프로젝트를 진행하면서 ListView Control을 하며 어려웠던 부분을 정리하였습니다.

 

👉 기본 환경

- Language: C#, xaml

- IDE: Visual Basic 2022


Style을 ResourceDictionary에 선언해두고 필요한 파일에서만 적용하고 싶었는데, 

Resource의 key를 설정하는 부분에서 반나절 넘게 해멨습니다.

 

코드를 정리하고나니, 두 줄뿐이었지만 잊기 전에 다시 기록해둡니다.

 

ResourceDictionary

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
<ResourceDictionary>
    <SolidColorBrush x:Key="PrimaryColor" Color="#FF0000"/>
    <Style x:Key="ScrollBarTrackThumb" TargetType="{x:Type Thumb}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Thumb}">
                    <Grid x:Name="Grid">
                        <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                                   Width="Auto" Height="Auto"
                                   Fill="Transparent" />
                        <Border x:Name="CornerScrollBarRectangle"
                                CornerRadius="5"
                                HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                                Width="Auto" Height="Auto"
                                Margin="0,1,0,1"
                                Background="{TemplateBinding Background}" />
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="Tag" Value="Horizontal">
                            <Setter TargetName="CornerScrollBarRectangle" Property="Width" Value="Auto" />
                            <Setter TargetName="CornerScrollBarRectangle" Property="Height" Value="6" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

 

Window / UserControl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<UserControl>
    <UserControl.Resources>
        <ResourceDictionary>
            <converter:BooleanToVisibleConverter x:Key="boolVisibleInverter"/>
            <RosourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="../Styles/CustomStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    
    <!-- 생략 -->
 
    <!-- 암시적 스타일: TargetType="{x:Type Thumb}"에 따라 ScrollViewer에 Style 적용 -->
    <ScrollViewer>
        <!-- 명시적 스타일: ResourceDictionary 선언된 Key값 선언 -->
        <Grid Background="{StaticResource PrimaryColor}"></Grid>
    </ScrollViewer>
</UserControl>

 

* ResourceDictionary
    * 일반적으로 x:Key 없이 Resources 또는 ResourceDictionary.MergedDictionaries 내에서 직접 사용
    * 만일, 리소스 구별이 필요할 경우 x:Key 값을 설정해야 함
* ResourceDictionary.MergedDictionaries
    * 리소스를 전역적으로 적용할 목적으로 사용되므로 x:Key가 필요하지 않음
    * 여러 리소스 사전을 하나로 합치는 것이 목적

        → 각 리소스 사전 자체에 대한 키를 설정하는 것이 아니라, 그 안에 포함된 개별 리소스에 x:Key를 설정
        * ResourceDictionary로 연결하면, 개별 요소에서 key값을 통해 Style에 접근 가능

 

'C# > WPF' 카테고리의 다른 글

[WPF] ListView와 SelectedItem 초기화  (0) 2024.03.31
[WPF] Converter와 Visibility  (0) 2024.03.18
[WPF] Popup Control  (1) 2024.03.17
[WPF] 검색과 정렬  (0) 2024.03.10
[WPF] ComboBox와 SelectedItem  (0) 2024.03.10

최근에 프로젝트를 진행하면서 ListView Control을 하며 어려웠던 부분을 정리하였습니다.

 

👉 기본 환경

- Language: C#, xaml

- IDE: Visual Basic 2022


Popup 태그를 처음 사용해보았는데, 간단하게 MVVM패턴에 맞춰

* Placement 컨트롤

* IsOpen 컨트롤

를 했던 방법을 정리해봅니다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<StackPanel Grid.Column="2"
            Orientation="Horizontal"
            HorizontalAlignment="Center"
            >
    <TextBlock Name="statusTextBlock" 
               HorizontalAlignment="Right" VerticalAlignment="Center" 
               Text="Status: "
               />
    <TextBlock Name="selectedStatus"
               HorizontalAlignment="Right" VerticalAlignment="Center" 
               Text="{Binding SelectedStatus}"
               />
</StackPanel>
<Popup IsOpen="True" PlacementTarget="{Binding ElementName=selectedStatus}" Placement="Bottom">
    <ListBox>
        <ListBoxItem Content="🤓To Do"/>
        <ListBoxItem Content="🤗Done"/>
    </ListBox>
</Popup>

Popup 태그를 선언하고 팝업창에 보여줄 내용을 태그 안에 선언합니다.

먼저 위치를 확인하기 위해 Popup창의 open 상태(IsOpen)를 True로 설정해둡니다.

 

팝업창의 위치는 여러가지 방법으로 설정할 수 있는데,

저는 콤보박스처럼 보이기위해 SelectedStatus 밑에 위치를 시켰습니다.

 

위치의 기준이 되는 element에 name을 부여하고 PlacementTarget에 지정하고

PlacementTarget 기준으로 어디에 위치할지를 Placement에 선언합니다.

 

 

이런 모양새로 팝업창이 늘 나타납니다. 위치가 마음에 들지만.. 묘하게 창 안쪽으로 들어가줬으면 하는 마음에 위치를 조정해봅니다.

 

저는 SelectedStatus 자리에 값이 들어올 때, Status라는 TextBlock이 움직이는 게 싫어서 Width를 고정으로 입력하니 알맞은 위치에 들어가 따로 조정을 하지 않았지만,

Popup 태그의 Property로 수평은 HorizontalOffset, 수직은 VerticalOffset으로 추가적인 위치 조정이 가능합니다.

 

위치를 정했으니, Popup의 Open 상태를 컨트롤해야 합니다.

 

1. Popup 태그의 isOpen을 bool 타입 변수와 바인딩하고 초기값을 false로 설정합니다.

2. SelectedStatus 클릭 시에 isOpen값을 true로 변경하고

 🫠 Textblock 클릭 컨트롤이라 behaviors nuget 패키지를 다운받았습니다.

3. ListBoxItem이 클릭되면 isOpen이 닫히고, SelectedStatus에 선택한 값이 들어오게 수정합니다.

 🫠 한 김에 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
<StackPanel Grid.Column="2"
            Orientation="Horizontal"
            HorizontalAlignment="Center"
            >
    <TextBlock Name="statusTextBlock" 
               VerticalAlignment="Center" 
               Text="Status: "
               />
    <TextBlock Name="selectedStatus"
               VerticalAlignment="Center" 
               Text="{Binding SelectedStatus}"
               Width="60"
               >
        <b:Interaction.Triggers>
            <b:EventTrigger EventName="PreviewMouseLeftButtonDown">
                <b:InvokeCommandAction Command="{Binding IsStatusPopupOpenCommand}" />
            </b:EventTrigger>
        </b:Interaction.Triggers>
    </TextBlock>
</StackPanel>
<Popup IsOpen="{Binding IsStatusPopupOpen}" 
       PlacementTarget="{Binding ElementName=selectedStatus}" Placement="Bottom"
       HorizontalOffset="0" VerticalOffset="5"
       >
    <ListBox SelectedValue="{Binding SelectedStatus, Mode=TwoWay}" SelectedValuePath="Content">
        <b:Interaction.Triggers>
            <b:EventTrigger EventName="SelectionChanged">
                <b:InvokeCommandAction Command="{Binding ChangeSelectedStatusCommand}" 
                                       CommandParameter="{Binding SelectedItem.Content, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"
                                       />
            </b:EventTrigger>
        </b:Interaction.Triggers>
        <ListBoxItem Content="🐳All"/>
        <ListBoxItem Content="🤓To Do"/>
        <ListBoxItem Content="🤗Done"/>
    </ListBox>
</Popup>

 

우선, TextBlock과 ListBox의 SelectedValue를 같은 변수에 바인딩을 해서 선택된 값이 TextBlock에 입력되게 하였습니다.

그리고 Textblock에 클릭 이벤트를 주고 싶어서 Behaviors를 사용했습니다.

 

그 다음, 팝업창은 열림 여부를 변수로 바인딩해주었습니다.

그 안의 ListBox에서는 SelectedValue로 선택된 값을 사용할 수 있게 변수와 바인딩하였고,

SelectionChanged 이벤트를 만들어서 팝업창을 닫고 ListView 아이템을 조건에 맞게 필터링하였습니다.

 

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
#region Properties
private bool _isStatusPopupOpen = false;
 
public bool IsStatusPopupOpen
{
    get { return _isStatusPopupOpen; }
    set 
    {
        _isStatusPopupOpen = value;
        OnPropertyChanged("IsStatusPopupOpen");
    }
}
 
private string _selectedStatus = "🐳All";
 
public string SelectedStatus
{
    get { return _selectedStatus; }
    set
    {
        _selectedStatus = value;
        OnPropertyChanged("SelectedStatus");
    }
}
 
#endregion
 
 
 
#region Commands
private ICommand _isStatusPopupOpenCommand;
public ICommand IsStatusPopupOpenCommand => _isStatusPopupOpenCommand ?? new RelayCommand(IsStatusPopupOpenEvent);
private ICommand _changeSelectedStatusCommand;
public ICommand ChangeSelectedStatusCommand => _changeSelectedStatusCommand ?? new RelayCommand(ChangeSelectedStatusEvent);
 
#endregion
 
 
 
#region Commands
private void IsStatusPopupOpenEvent(object obj)
{
    IsStatusPopupOpen = true;
}
 
private void ChangeSelectedStatusEvent(object selectedStatus)
{
    IsStatusPopupOpen = false;
 
    switch (selectedStatus)
    {
        case "🐳All":
            ToDoList = new ObservableCollection<ToDoItem>(OriginalToDoList);
            break;
        case "🤓To Do":
            ToDoList = new ObservableCollection<ToDoItem>(OriginalToDoList.Where(x => x.isComplete == false));
            break;
        case "🤗Done":
            ToDoList = new ObservableCollection<ToDoItem>(OriginalToDoList.Where(x => x.isComplete == true));
            break;
        default:
            return;
    }
}
 
#endregion

 

 

 

⭐ 팝업창은 Popup 태그를 활용할 수 있다🔥.

 

 

 

📚 참고 자료

 

Microsoft.Xaml.Behaviors.Wpf를 사용하기 Part2

2023.02.06 - [WPF .NET] - Microsoft.Xaml.Behaviors.Wpf를 사용하기 Part1 Microsoft.Xaml.Behaviors.Wpf에서 가장 많이 사용되는 Action들에 대해서 알아보도록 하겠습니다. Action들은 Trigger를 이용해서 특정 작업을 쉽게

kaki104.tistory.com

 

'C# > WPF' 카테고리의 다른 글

[WPF] Converter와 Visibility  (0) 2024.03.18
[WPF] ResourceDictionary  (0) 2024.03.18
[WPF] 검색과 정렬  (0) 2024.03.10
[WPF] ComboBox와 SelectedItem  (0) 2024.03.10
[WPF] ListView와 SelectedItem  (0) 2024.03.10