본문 바로가기
대충 만들면서 배우자/키워드검색

[C#/WPF]네이버 연관 검색어 프로그램 7(WPF UI : 결과)

by 프갭 2023. 3. 15.

네이버 API 및 11번가 Open API를 이용해 키워드 검색프로그램을 만들면서 개발의 방법 및 스킬을 늘리는게 목적입니다.

기본 베이스가 되는 프로그램 언어는 c#(WPF)로 진행 할 생각 입니다

1) WPF UI 프로젝트 생성

EEH.DLL,EEH.WPF.DLL  참조합니다.(이전 시간에 만들었던 공통 프로젝트를 사용하기 위해 참조)

UI 디자인을 위해 NuGet에서 ModernUI.CoreWPF 를 참조 합니다.

 

(1)  App.xaml

<Application x:Class="EEH.WPF.UI.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:EEH.WPF.UI"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.xaml" />
                <ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.Dark.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

 

ModernUI Theme 적용을 위에 App.xaml 파일에 Application.Resources 를 추가 합니다.

 

using FirstFloor.ModernUI.Presentation;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace EEH.WPF.UI
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            AppearanceManager.Current.ThemeSource = AppearanceManager.LightThemeSource;//ModernUI Theme 적용
         
        }
    }
}

(2) MainWindow.xaml

<mui:ModernWindow x:Class="EEH.WPF.UI.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:mui="http://firstfloorsoftware.com/ModernUI"
        xmlns:local="clr-namespace:EEH.WPF.UI"
        mc:Ignorable="d" 
        ContentSource="/Keyword/Views/KeywordSearch.xaml"
        Title="MainWindow" Height="800" Width="1400">
    <mui:ModernWindow.MenuLinkGroups>
        <mui:LinkGroup DisplayName="KEYWORD">
            <mui:LinkGroup.Links>
                <mui:Link DisplayName="SEARCH" Source="/Keyword/Views/KeywordSearch.xaml"></mui:Link>
                <mui:Link DisplayName="API INFO" Source="/Keyword/Views/Settings.xaml"></mui:Link>
            </mui:LinkGroup.Links>
        </mui:LinkGroup>
    </mui:ModernWindow.MenuLinkGroups>
    <Grid>

    </Grid>
</mui:ModernWindow>

 

using FirstFloor.ModernUI.Windows.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace EEH.WPF.UI
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : ModernWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

(3) KeyworkdSearch.xaml

<Page x:Class="EEH.WPF.UI.Keyword.Views.KeywordSearch"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008
      xmlns:local="clr-namespace:EEH.WPF.UI.Keyword.Views"
      xmlns:mui="http://firstfloorsoftware.com/ModernUI"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="KeywordSearch">

    <Grid  Style="{StaticResource ContentRoot}">
        <Grid.RowDefinitions>
            <RowDefinition Height="50px"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <StackPanel VerticalAlignment="Center" Orientation="Horizontal" >
                <TextBox Text="{Binding SearchText}" Height="30" MinWidth="150" VerticalAlignment="Center" VerticalContentAlignment="Center"></TextBox>
                <Button Command="{Binding OnSearchCommand}" Content="검색" Width="100" Height="30" Margin="10,0,0,0"></Button>
                <Button Command="{Binding OnSearchEcommerceCommand}" Content="분석" Width="100" Height="30" Margin="10,0,0,0"></Button>
            </StackPanel>
        </Grid>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="200"/>
                <ColumnDefinition Width="2"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <ListBox ItemsSource="{Binding Results}" DisplayMemberPath="Keyword" SelectedItem="{Binding SelectedItem}" ></ListBox>
            <GridSplitter Grid.Column="1"></GridSplitter>
            <DataGrid Grid.Column="2" CanUserAddRows="False" CanUserDeleteRows="False"
                      IsReadOnly="True" AutoGenerateColumns="False"  FrozenColumnCount="1"
                      ItemsSource="{Binding SelectedItem.KeywordList}">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="연관검색어" Binding="{Binding RelKeyword}"  />
                    <DataGridTextColumn Header="경쟁정도" Binding="{Binding PlAvgDepth}" />
                    <DataGridTextColumn Header="월평균노출광고상태" Binding="{Binding CompIdx}" />
                    <DataGridTextColumn Header="월간검색수" Binding="{Binding MonthlyQcCnt,StringFormat=\{0:N0\}}" />
                    <DataGridTextColumn Header="월평균클릭수" Binding="{Binding MonthlyAveClkCnt}" />
                    <DataGridTextColumn Header="월평균클릭률" Binding="{Binding MonthlyAveCtr}" />

                    <DataGridTextColumn Header="네이버상품수" Binding="{Binding EcommerceNaverCnt,StringFormat=\{0:N0\}}" />
                    <DataGridTextColumn Header="11번가상품수" Binding="{Binding Ecommerce11stCnt,StringFormat=\{0:N0\}}" />
                    <DataGridTextColumn Header="인터파크상품수" Binding="{Binding EcommerceInterparkCnt,StringFormat=\{0:N0\}}" />
                    
                </DataGrid.Columns>
                
            </DataGrid>
        </Grid>
        <Border Grid.RowSpan="2" Background="Black" Opacity="0.8" Visibility="{Binding VisibilityProgressBar}">
            <Grid>
                <StackPanel VerticalAlignment="Center">
                    <ProgressBar Visibility="Visible" Margin="20" Height="20"  IsIndeterminate="False"   Minimum="0" Maximum="100" Value="{Binding ProgressValue}" ></ProgressBar>
                    <TextBlock Foreground="White" HorizontalAlignment="Center" Text="{Binding ProgressText}"></TextBlock>
                    <Button Content="분석종료" Width="100" Style="{StaticResource SystemButtonLink}" Command="{Binding OnStopCommand}"  ></Button>
                </StackPanel>
            </Grid>
        </Border>
    </Grid>
</Page>


using EEH.WPF.UI.Keyword.ViewModels;
using System.Windows.Controls;

namespace EEH.WPF.UI.Keyword.Views
{
    /// <summary>
    /// KeywordSearch.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class KeywordSearch : Page
    {
        public KeywordSearch()
        {
            InitializeComponent();
            this.DataContext = new KeywordSearchViewModel();
        }   
    }
}

(4) KeyworkdSearchViewModel.cs

using EEH.Component.Ecommerce;
using EEH.Component.Keyword.Naver;
using EEH.Component.Keyword.Naver.Models;

using EEH.Utils;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace EEH.WPF.UI.Keyword.ViewModels
{
    public class KeywordSearchViewModel : BaseViewModel
    {
        string progressText;
        public string ProgressText
        {
            get { return progressText; }
            set { progressText = value; OnPropertyCanged("ProgressText"); }
        }
        double progressValue;
        public double ProgressValue
        {
            get { return progressValue; }
            set { progressValue = value; OnPropertyCanged("ProgressValue"); }
        }
        Visibility visibilityProgressBar = Visibility.Hidden;
        public Visibility VisibilityProgressBar
        {
            get { return visibilityProgressBar; }
            set { visibilityProgressBar = value; OnPropertyCanged("VisibilityProgressBar"); }
        }
        string searchText;
        public string SearchText
        {
            get { return searchText; }
            set { searchText = value; OnPropertyCanged("SearchText"); }
        }

        ObservableCollection<KeywordsTool> results;
        public ObservableCollection<KeywordsTool> Results
        {
            get { return results; }
            set { results = value; OnPropertyCanged("Results"); }
        }
        KeywordsTool selectedItem;
        public KeywordsTool SelectedItem
        {
            get { return selectedItem; }
            set { selectedItem = value; OnPropertyCanged("selectedItem"); }
        }
        public BaseCommand OnSearchCommand { get; set; }
        public BaseCommand OnSearchEcommerceCommand { get; set; }
        public BaseCommand OnStopCommand { get; set; }
        public BaseCommand OnSaveCommand { get; set; }
        List<IOpenApi> ecommerceApis;
        public KeywordSearchViewModel()
        {
            ecommerceApis = new List<IOpenApi>();
            ecommerceApis.Add(new OpenApiNaver());
            ecommerceApis.Add(new OpenApi11st());
            ecommerceApis.Add(new SearchInterpark());

            Results = new ObservableCollection<KeywordsTool>();
            OnSearchCommand = new BaseCommand();
            OnSearchCommand.ExecuteHandler = async (param) =>
            {

                NaverAPIClient client = new NaverAPIClient();
                KeywordsTool result = await client.Search(SearchText);
                Results.Add(result);
                SearchText = string.Empty;
                SelectedItem = result;
                Save();
                OnPropertyCanged("KeywordResult");
            };

            OnSearchEcommerceCommand = new BaseCommand();
            OnSearchEcommerceCommand.ExecuteHandler = (param) =>
            {
                if (SelectedItem.ExNotNull() && ecommerceApis.ExNotNull())
                {
                    VisibilityProgressBar = Visibility.Visible;

                    double totalCnt = 0;
                    double progressCnt = 0;

                    foreach (IOpenApi api in ecommerceApis)
                    {

                        foreach (KeywordModel keyword in SelectedItem.KeywordList)
                        {
                            totalCnt += 1;
                            long cnt = 0;
                            QueueAsync.Instance.AddTask(async () =>
                            {
                                cnt = await api.GetProductTotalCnt(keyword.RelKeyword);
                            }, (r, ErrorContext) =>
                            {
                                if (api.Type == EcommerceType.ETINTERPARK)
                                {
                                    keyword.EcommerceInterparkCnt = cnt;
                                }
                                else if (api.Type == EcommerceType.ET11ST)
                                {
                                    keyword.Ecommerce11stCnt = cnt;
                                }
                                else if (api.Type == EcommerceType.ETNAVER)
                                {
                                    keyword.EcommerceNaverCnt = cnt;
                                }

                                progressCnt += 1;
                                ProgressValue = progressCnt * 100 / totalCnt;

                                ProgressText = string.Format("{0}/{1} {2}%", progressCnt, totalCnt, Math.Round(progressValue, 2));
                                if (totalCnt == progressCnt)
                                {
                                    Save();
                                    HideProgress();
                                }

                            });
                        }


                    }
                }
            };

            OnStopCommand = new BaseCommand();
            OnStopCommand.ExecuteHandler = (param) =>
            {
                QueueAsync.Instance.Stop();
                Save();
                HideProgress();
            };

            Load();
        }
        void HideProgress()
        {
            VisibilityProgressBar = Visibility.Collapsed;
            ProgressValue = 0;
        }
        void Save()
        {
            string strJson = SelectedItem.ExJsonSerializeString();
            strJson.ExSaveForDataFolder("KEYWORD", SelectedItem.Keyword);


        }

        void Load()
        {
            List<string> keywords = "KEYWORD".ExListForDataFolder();

            foreach (string keyword in keywords)
            {
                string strJson = "KEYWORD".ExLoadForDataFolder(keyword);
                KeywordsTool r = strJson.ExDeserializeObject<KeywordsTool>();
                if (r.ExNotNull())
                    Results.Add(r);
            }
            if (Results.Count > 0)
            {
                SelectedItem = Results[0];
            }

        }
    }
}

(5) Settings.xaml

<Page x:Class="EEH.WPF.UI.Keyword.Views.Settings"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008
             xmlns:local="clr-namespace:EEH.WPF.UI.Keyword.Views"
      xmlns:pwd="clr-namespace:EEH.WPF;assembly=EEH.WPF"
      xmlns:mui="http://firstfloorsoftware.com/ModernUI"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid  Style="{StaticResource ContentRoot}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="30px"></RowDefinition>
        </Grid.RowDefinitions>
        <ScrollViewer>
            <StackPanel>
                <TextBlock Text="네이버 광고 API INFO" Style="{StaticResource Heading2}" Margin="0,3,0,3" ></TextBlock>
                <Grid Margin="0,3,0,3">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                        <ColumnDefinition Width="2*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="Customer ID" VerticalAlignment="Center" />
                    <PasswordBox Grid.Column="1"  VerticalAlignment="Center" pwd:PasswordHelper.Password="{Binding NaverCustomerID,Mode=TwoWay}"  />
                </Grid>
                <Grid Margin="0,3,0,3"> 
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                        <ColumnDefinition Width="2*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="Api Key" VerticalAlignment="Center" />
                    <PasswordBox Grid.Column="1" VerticalAlignment="Center" pwd:PasswordHelper.Password="{Binding NaverApiKey,Mode=TwoWay}"  />
                </Grid>
                <Grid Margin="0,3,0,3">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                        <ColumnDefinition Width="2*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="Secret Key" VerticalAlignment="Center" />
                    <PasswordBox Grid.Column="1"  VerticalAlignment="Center" pwd:PasswordHelper.Password="{Binding NaverSecretKey,Mode=TwoWay}"  />
                </Grid>

                <TextBlock Text="네이버 OPEN API INFO" Style="{StaticResource Heading2}" Margin="0,3,0,3" ></TextBlock>

                <Grid Margin="0,3,0,3">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                        <ColumnDefinition Width="2*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="Client ID" VerticalAlignment="Center" />
                    <PasswordBox Grid.Column="1"  VerticalAlignment="Center" pwd:PasswordHelper.Password="{Binding OpenApiNaverClientID,Mode=TwoWay}"  />
                </Grid>
                <Grid Margin="0,3,0,3">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                        <ColumnDefinition Width="2*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="Secret Key" VerticalAlignment="Center" />
                    <PasswordBox Grid.Column="1" VerticalAlignment="Center" pwd:PasswordHelper.Password="{Binding OpenApiNaverSecretKey,Mode=TwoWay}"  />
                </Grid>

                <TextBlock Text="11번가 OPEN API INFO" Style="{StaticResource Heading2}" Margin="0,3,0,3" ></TextBlock>
                <Grid Margin="0,3,0,3">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                        <ColumnDefinition Width="2*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="Key" VerticalAlignment="Center" />
                    <PasswordBox Grid.Column="1" VerticalAlignment="Center" pwd:PasswordHelper.Password="{Binding OpenApi11stKey,Mode=TwoWay}"  />
                </Grid>
            </StackPanel>
        </ScrollViewer>
        <Button Grid.Row="1" Style="{StaticResource BaseButtonStyle}" Command="{Binding SaveCommand}" Content="저장"></Button>
            
    </Grid>
</Page>


using EEH.WPF.UI.Keyword.ViewModels;
using System.Windows.Controls;

namespace EEH.WPF.UI.Keyword.Views
{
    /// <summary>
    /// Settings.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class Settings : Page
    {
        public Settings()
        {
            InitializeComponent();
            this.DataContext = new SettingsViewModel();
        }
    }
}

(6) Settings.xaml

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace EEH.WPF.UI.Keyword.ViewModels
{
    public class SettingsViewModel : BaseViewModel
    {

        string naverCustomerID;
        public string NaverCustomerID
        {
            get
            {
                return naverCustomerID;
            }
            set
            {
                naverCustomerID = value;
                OnPropertyCanged("NaverCustomerID");
            }
        }

        string naverApiKey;
        public string NaverApiKey
        {
            get
            {
                return naverApiKey;
            }
            set
            {
                naverApiKey = value;
                OnPropertyCanged("NaverApiKey");
            }
        }
        string naverSecretKey;
        public string NaverSecretKey
        {
            get
            {
                return naverSecretKey;
            }
            set
            {
                naverSecretKey = value;
                OnPropertyCanged("NaverSecretKey");
            }
        }
        string openApiNaverClientID;
        public string OpenApiNaverClientID
        {
            get
            {
                return openApiNaverClientID;
            }
            set
            {
                openApiNaverClientID = value;
                OnPropertyCanged("OpenApiNaverClientID");
            }
        }
        string openApiNaverSecretKey;
        public string OpenApiNaverSecretKey
        {
            get
            {
                return openApiNaverSecretKey;
            }
            set
            {
                openApiNaverSecretKey = value;
                OnPropertyCanged("OpenApiNaverSecretKey");
            }
        }
        string openApi11stKey;
        public string OpenApi11stKey
        {
            get
            {
                return openApi11stKey;
            }
            set
            {
                openApi11stKey = value;
                OnPropertyCanged("OpenApi11stKey");
            }
        }

        public BaseCommand SaveCommand { get; set; }

        public SettingsViewModel()
        {
            SaveCommand = new BaseCommand();
            SaveCommand.ExecuteHandler = (param) =>
            {

                APIInfoSettings.Default.KeywordSearchNaverCustomerID = NaverCustomerID;
                APIInfoSettings.Default.KeywordSearchNaverApiKey = NaverApiKey;
                APIInfoSettings.Default.KeywordSearchNaverSecret = NaverSecretKey;

                APIInfoSettings.Default.OpenApiNaverClientID = OpenApiNaverClientID;
                APIInfoSettings.Default.OpenApiNaverSecret = OpenApiNaverSecretKey;

                APIInfoSettings.Default.OpenApi11StKey = OpenApi11stKey;
                APIInfoSettings.Default.Save();


            };
            Init();
        }

        public void Init()
        {

            NaverCustomerID = APIInfoSettings.Default.KeywordSearchNaverCustomerID;
            NaverApiKey = APIInfoSettings.Default.KeywordSearchNaverApiKey;
            NaverSecretKey = APIInfoSettings.Default.KeywordSearchNaverSecret;

            OpenApiNaverClientID = APIInfoSettings.Default.OpenApiNaverClientID;
            OpenApiNaverSecretKey = APIInfoSettings.Default.OpenApiNaverSecret;

            OpenApi11stKey = APIInfoSettings.Default.OpenApi11StKey;
        }
    }
}

 

(7) 결과

EEH.WPF.UI 시작 프로젝트로 설정후 F5 클릭 하면 빌드 되면서 프로그램이 실행 됩니다.

API 정보를 입력후 저장

원하는 키워드 검색

분석 클릭하면 네이버,11번가,인터파크의 상품수 검색합니다

이상으로 키워드 검색 프로그램을 만들어 봤습니다.

코드 분석 후 기능 추가하면 개발에 큰도움이 될거 같습니다.

다음에 만들어볼 프로그램은 키움 증권의 API를 이용하여 조건식 자동 매수,매도 프로그램을 만들어 보겠습니다.

감사합니다.

반응형

댓글