본문 바로가기
Development/Toy Projects

[프로젝트] 심심이(SimSimi) API를 이용한 채팅 프로그램 만들기 😀🧨 (하편)

by Kyunghoon Kim 2020. 6. 8.

안녕하세요 이번 포스트는 심심이 API를 이용한 채팅 프로그램 만들기 1편과 이어지는 내용입니다. 1편에 대한 내용이 있어야 2편을 진행할 수 있으므로 아직 보고 오시지 않은 분들께는 1편을 먼저 보고 오시는 것을 추천드립니다!

 

https://devkyunghoon.tistory.com/8

 

 

🧨 1편 - 심심이(SimSimi) API를 이용한 채팅 프로그램 만들기 😀

안녕하세요 이번 포스트에서는 심심이(SimSimi) API를 이용하여 대화를 할 수 있는 프로그램을 만들어 보려고 합니다. 웹상에서만 심심이와 대화를 할 수 있었던 것을 보고 윈도우 응용프로그램으�

devkyunghoon.tistory.com

 


이제부터 본격적으로 2편 - CURL 요청을 통해 받아오는 메시지와 사용자의 메시지를 UI에 나타내 보는 시간을 가져보겠습니다. 지금까지 해왔었던 급식 정보 파싱, MySQL DB 연동에서의 UI와는 다르게 채팅 프로그램이다 보니 조금 더 예쁘게 꾸미려고 노력해 보았습니다.😅 그렇기에 웹에서의 심심이 대화 UI를 최대한 따라 만들어 보겠습니다.

 

웹 버전의 SimSimi UI 입니다.

 

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
<Window x:Class="SimSimi_Talk.MainWindow"
        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:SimSimi_Talk"
        mc:Ignorable="d"
        Title="SimSimi" Height="800" Width="1000"
        KeyDown="Window_KeyDown">
 
    <Window.Resources>
        <Style x:Key="tbStyle" TargetType="{x:Type TextBox}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TextBox}">
                        <Border x:Name="Border"
                                Padding="10 0 0 0"
                                Background="Transparent"
                                BorderBrush="Transparent"
                                CornerRadius="3"
                                BorderThickness="1">
 
                            <Grid Height="50">
                                <Grid>
                                    <Grid Margin="7 0 20 0">
                                        <ScrollViewer x:Name="PART_ContentHost"
                                                      VerticalAlignment="Center"
                                                      Cursor="IBeam"/>
                                        <TextBlock x:Name="WaterMark" Foreground="#E2E2E2"
                                                   Margin="2 0 0 0"
                                                   VerticalAlignment="Center"
                                                   Visibility="Collapsed"
                                                   FontWeight="Medium"
                                                   Text="{TemplateBinding Tag}"/>
                                    </Grid>
                                </Grid>
                            </Grid>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="Text" Value="">
                                <Setter TargetName="WaterMark" Property="Visibility" Value="Visible"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    
    <Grid Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="8*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
 
        <Grid Grid.Row="0" x:Name="gdTitle">
            <Border Margin="0,0,0,10" BorderBrush="Transparent" BorderThickness="1" Background="White" CornerRadius="0">
                <Border.Effect>
                    <DropShadowEffect ShadowDepth="2" BlurRadius="20" Color="#E2E2E2"/>
                </Border.Effect>
            </Border>
 
            <Border BorderThickness="1" BorderBrush="Transparent">
                <TextBlock Text="SimSimi" Margin="30,0,0,0" FontSize="30" 
                           FontFamily="나눔고딕" FontWeight="ExtraBold"
                           VerticalAlignment="Center" HorizontalAlignment="Left"/>
            </Border>
        </Grid>
 
        <Grid Grid.Row="1" x:Name="gdChatContent">
            <Border Margin="10" BorderBrush="Transparent" BorderThickness="1" Background="White" CornerRadius="0">
                <Border.Effect>
                    <DropShadowEffect ShadowDepth="2" BlurRadius="20" Color="#E2E2E2"/>
                </Border.Effect>
            </Border>
 
            <Border BorderThickness="1" BorderBrush="Transparent" Margin="10">
                <Grid Margin="10">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    
                    <Grid Grid.Column="0">
                        <ListView x:Name="lvSimSimiMessageList" ItemsSource="{Binding SimSimiMsgItems}"
                              ScrollViewer.HorizontalScrollBarVisibility="Disabled"  
                              ScrollViewer.VerticalScrollBarVisibility="Hidden"
                              ScrollViewer.CanContentScroll="False"
                              BorderThickness="0">
                            <ListView.ItemContainerStyle>
                                <Style TargetType="ListViewItem">
                                    <Setter Property="Background" Value="Transparent"/>
                                    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
                                    <Setter Property="Focusable" Value="False"/>
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="{x:Type ListViewItem}">
                                                <ContentPresenter/>
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </ListView.ItemContainerStyle>
 
                            <ListView.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel Margin="10">
                                        <StackPanel.Resources>
                                            <Style TargetType="TextBlock">
                                                <Setter Property="FontFamily" Value="나눔스퀘어_ac"/>
                                                <Setter Property="FontSize" Value="25"/>
                                                <Setter Property="TextWrapping" Value="Wrap"/>
                                                <Setter Property="Foreground" Value="white"/>
                                            </Style>
                                        </StackPanel.Resources>
 
                                        <TextBlock Height="50"/>
 
                                        <Border Background="#32353f" CornerRadius="20" HorizontalAlignment="Left">
                                            <Grid>
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width="1*"/>
                                                    <ColumnDefinition Width="9*"/>
                                                </Grid.ColumnDefinitions>
                                                <Grid Grid.Column="0">
                                                    <Image Source="/Assets/SimSimi.png" Height="40" VerticalAlignment="Top" Margin="10,5,0,0"/>
                                                </Grid>
                                                <Grid Grid.Column="1">
                                                    <TextBlock x:Name="tbSimsimi" Margin="10"  
                                                               Text="{Binding SimSimiMessage, StringFormat={}심심이 : {0}}"/>
                                                </Grid>
                                            </Grid>
                                        </Border>
                                    </StackPanel>
                                </DataTemplate>
                            </ListView.ItemTemplate>
                        </ListView>
                    </Grid>
                    
                    <Grid Grid.Column="1">
                        <ListView x:Name="lvUserMessageList" ItemsSource="{Binding UserMsgItems}" Tag="{Binding SimSimiMsgItems}"
                                  ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
                                  ScrollViewer.VerticalScrollBarVisibility="Hidden"
                                  ScrollViewer.CanContentScroll="False"
                                  BorderThickness="0">
                            <ListView.ItemContainerStyle>
                                <Style TargetType="ListViewItem">
                                    <Setter Property="Background" Value="Transparent"/>
                                    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
                                    <Setter Property="Focusable" Value="False"/>
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="{x:Type ListViewItem}">
                                                <ContentPresenter />
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </ListView.ItemContainerStyle>
 
                            <ListView.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel Margin="10">
                                        <StackPanel.Resources>
                                            <Style TargetType="TextBlock">
                                                <Setter Property="FontFamily" Value="나눔스퀘어_ac"/>
                                                <Setter Property="FontSize" Value="25"/>
                                                <Setter Property="TextWrapping" Value="Wrap"/>
                                            </Style>
                                        </StackPanel.Resources>
 
                                        <Border Background="#ffe34f" CornerRadius="20" HorizontalAlignment="Right">
                                            <TextBlock x:Name="tbUser" Margin="10" 
                                                       Text="{Binding UserMessage, StringFormat={}나 : {0}}">
                                            </TextBlock>
                                        </Border>
                                        <TextBlock Height="50"/>
                                    </StackPanel>
                                </DataTemplate>
                            </ListView.ItemTemplate>
                        </ListView>
                    </Grid>
                </Grid>
            </Border>
        </Grid>
 
        <Grid Grid.Row="2" x:Name="gdUserMsg">
            <Border Margin="10" BorderBrush="Transparent" BorderThickness="1" Background="White" CornerRadius="0">
                <Border.Effect>
                    <DropShadowEffect ShadowDepth="2" BlurRadius="20" Color="#E2E2E2"/>
                </Border.Effect>
            </Border>   
 
            <Border BorderThickness="1" BorderBrush="Transparent">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="9*"/>
                        <ColumnDefinition Width="1*"/>
                    </Grid.ColumnDefinitions>
 
                    <Grid Grid.Column="0">
                        <TextBox x:Name="tbUserMsg"
                                 Style="{StaticResource tbStyle}" 
                                 VerticalAlignment="Center"
                                 Width="900"
                                 FontSize="18"
                                 TextWrapping="Wrap"
                                 BorderBrush="Transparent" 
                                 Tag="메시지를 입력하세요."/>
                    </Grid>
 
                    <Grid Grid.Column="1">
                        <Button Background="Transparent"
                                BorderBrush="Transparent"
                                Height="30" Width="30"
                                Click="btnSendMsg_Click">
                            <ContentControl>
                                <Image Source="/Assets/SendButtonImg.png"/>
                            </ContentControl>
                        </Button>
                    </Grid>
                </Grid>
            </Border>
        </Grid>
    </Grid>
</Window>
 
cs

 

 

먼저 Window.Resources 부터 볼게요. 이 곳에는 Control에 대한 Style을 적용하려고 미리 Style을 만들어 두는 것입니다. tbStyle은 TextBlock의 워터마크 효과를 주기 위해서 넣어두었습니다. 그다음 Grid.RowDefinition을 통해 전체적인 틀을 나누어 주었습니다. 아래의 화면을 참고하시면 되겠습니다.

 

 

전체적인 화면 UI 구조

 

첫 번째 Grid.Row에는 Tilte, 두 번째 Grid.Row에는 ListView 2개를 넣고 사용자의 메시지와 심심이의 메시지를 나타내 줄 것입니다. 마지막 세 번째 Grid.Row에는 메시지를 입력하고 전송할 수 있도록 만들 것입니다. 따라서 위의 XAML에 대한 코드도 Grid.Row에 대해 구분되어 있기에 크게 어렵지 않게 확인할 수 있습니다.  조금 조심해야 할 것은 Grid.Row="1"에서 다시 Column을 나누어서 ListView가 각각 배치될 수 있도록 하였습니다. 다음으로는 MainWindow.xaml.cs에 대한 코드를 보도록 하겠습니다.

 

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
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
 
namespace SimSimi_Talk
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Loaded += MainWindow_Loaded;
        }
 
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            this.DataContext = App.simsimiViewModel;
        }
 
        private void btnSendMsg_Click(object sender, RoutedEventArgs e)
        {
            App.simsimiViewModel.GetSimSimiMessage(tbUserMsg.Text);
 
            ScrollToLast(lvUserMessageList);
            ScrollToLast(lvSimSimiMessageList);
 
            tbUserMsg.Text = string.Empty;
        }
 
        private void Window_KeyDown(object sender, KeyEventArgs e)
        {
            if(e.Key == Key.Return)
            {
                App.simsimiViewModel.GetSimSimiMessage(tbUserMsg.Text);
 
                ScrollToLast(lvUserMessageList);
                ScrollToLast(lvSimSimiMessageList);
 
                tbUserMsg.Text = string.Empty;
            }
        }
 
        #region Find Scroll To LastItem 
        public void ScrollToLast(ListView listView)
        {
            Debug.WriteLine(listView.Name);
 
            if (listView.Items.Count > 0)
            {
                if(listView.Name == "lvUserMessageList")
                {
                    ScrollToLastItem<Model.User>(listView, App.simsimiViewModel.UserMsgItems);
                }
                else if(listView.Name == "lvSimSimiMessageList")
                {
                    ScrollToLastItem<Model.SimSimi>(listView, App.simsimiViewModel.SimSimiMsgItems);
                }
            }
        }
 
        public void ScrollToLastItem<T>(ListView listView, ObservableCollection<T> collection) where T : class
        {
            listView.SelectedItem = listView.Items.GetItemAt(collection.Count - 1);
            listView.ScrollIntoView(listView.SelectedItem);
 
            ListViewItem item = listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem) as ListViewItem;
                    
            if(item != null)
            {
                item.Focus();
            }
        }
        #endregion
    }
}
 
cs

MainWIndow.xaml.cs 코드

 

위에서 부터 보도록 하겠습니다. 먼저 Loaded Event를 발생시켜 DataContext를 지정해주었습니다. 그리고 메시지 전송 버튼을 눌렀을 때에 대한 이벤트를 발생시켜 SimSimiViewModel에 있는 GetSimSimiMessage()에 대한 Text를 인자로 넘겨주었습니다. 그리고 ScrollToLast()와 ScrollToLastItem 메서드를 통해 메시지가 화면 이상의 범위를 넘어섰을 때 Scroll을 최근 입력한 메시지에 대해 Focus를 맞추어 주려고 하였습니다.

 

Debug를 통해 값을 찍어보니 마지막 메시지에 대해 ListViewItem으로 접근을 할 수는 있었지만 Focus()가 제대로 이루어지지 않았습니다. 또한 ListView안에 TextBlock으로 Binding이 되어있기 때문에 x:Name을 통해 접근을 할 수도 없었습니다. 그렇기에 다른 방법으로 최근의 메시지에 대해 접근을 시도해야 했습니다.

 

그렇게 찾아본 방법이 VisualTreeHelper 클래스의 GetChildrenCount의 메서드를 통해 ListView의 자식들에 대해서 접근을 한 후 그 자식들의 자식에 대해 다시 접근을 해서 ScrollViewer에 대한 Focus(최근 메시지로 이동)를 맞추어 주었습니다. 그렇기에 ScrollToLast()와 ScrollToLastItem <T> 메서드는 다음의 메서드를 통해 교체가 가능합니다. 이 메서드는 이전의 코드보다 코드라인 수도 짧고 더 효율적으로 사용이 가능했습니다.

 

1
2
3
4
5
6
7
8
9
public void ScrollToBottom(ListView listView)
    if (VisualTreeHelper.GetChildrenCount(listView) > 0)
    {
        Border border = (Border)VisualTreeHelper.GetChild(listView, 0);
        ScrollViewer scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
        scrollViewer.ScrollToBottom();
    }
}
cs

 

이전에 작성했던 ScrollToLast 메서드와 ScrollToLastItem<T> 메서드를 위의 ScrollToBottom으로 바꾸어 주세요. 이로써 모든 UI와 비하인드 코드에 대한 작성을 완료했습니다. 그렇다면 결과를 한 번 봐야겠죠?

 

완성된 UI

어떤가요? UI가 괜찮은가요? 나름 열심히 만든다고 만들어봤는데 괜찮은지 모르겠네요😅 이제는 직접 시연 영상으로 한번 보도록 할게요. 여기서 채팅 화면의 크기보다 메시지가 더 많아졌을 때 Focus()가 잘 맞춰지는지 확인해 보세요.

 

 


 

대화의 화면이 넘어갈 때 Focus()가 자동으로 맞추어지는 것을 확인해 보셨나요? 오늘은 여기까지 입니다. 프로젝트의 소스에 대한 자료는 제 GitHub 저장소에 올려두겠습니다. 아래의 링크를 참조해 주세요. 지금까지 1편과 2편으로 나누어 SimSimi와 대화를 할 수 있는 프로그램을 만들어 보았습니다. 감사합니다😊

 

https://github.com/KyungHoon0126/SimSimi-Talk

 

KyungHoon0126/SimSimi-Talk

심심이 채팅 (2020. 05. 25 ~ 2020. 06. 05). Contribute to KyungHoon0126/SimSimi-Talk development by creating an account on GitHub.

github.com

 

댓글