블로그 이미지
* Microsoft MVP - Windows Development 2014 ~ 2020 http://youtube.com/FutureOfDotNet kaki104

카테고리

List All (592)
Visual Studio (1)
Blazor (2)
Windows App(Universa.. (97)
Xamarin Forms (4)
Bot Framework (19)
Azure (9)
Windows 10 (52)
WPF (6)
Facebook News & Tips (158)
Windows 8&8.1 (113)
Windows Phone 8 (42)
Silverlight (37)
HTML5 & MVC4 (16)
Portable Class Library (2)
Uncategorised Tips a.. (3)
Kinect for Windows (2)
ETC (12)
kaki104 Scrap (4)
App News (11)
Total536,356
Today29
Yesterday59

블로그 이전을 성공적으로 마무리 했으니, 이제 강좌를 마무리 할 차례인 것 같다. 그런데 블로그 유입 통계를 보면 Search라는 검색어로 들어오는 유저들이 많은데.. 개발자들이 들어오는 것이 아닌 것 같은 불길한 느낌이 든다. -0- 또한, 어제 오늘 윈폰을 열심히 사용해 봤는데.. 아무래도 한글로 만들어진 엡은 찾아보기가 어렵다. 그러니, 강좌를 접한 분들은 꼭 하나씩 자신만의 엡을 만들어서 올려야 한다. 이렇게 자세하게 강좌를 올리는 이유가 쉽게 접근 할 수 있는 길을 하나 뚫어 주는 것이라고 생각하기 때문임을 잊으면 아니 된다.(잊으면 폭력행사 들어감 퍽!퍽~퍽@)

1. 어떤 버스의 노선 정보 인가?

MainPageViewModel.cs

BusRouteModel busCurrent;
/// <summary>
/// 선택된 버스 정보 프로퍼티
/// </summary>
public BusRouteModel BusCurrent
{
    get { return busCurrent; }
    set
    {
        busCurrent = value;
        FirePropertyChange("BusCurrent");
    }
}
프로퍼티를 추가한다.

SelectionChangedCommand에

//선택된 버스 정보 저장
BusCurrent = route;

코드 추가 F6키를 눌러서 빌드하고 Blend로 이동


1차 적으로 ContentPanel에 구역을 추가하고 상단 구역에 Grid를 추가 후 위와 같은 디자인을 만들었다. 지금 보이는 글씨들은 위치나 크기를 알아 보기 위해 직접 입력 해 놓은 데이터이다. 어느 정도 디자인이 완료가 되면 바인딩에 들어간다.

버스별 색상 입력
3100-1의정부라는 TextBlock은 Border로 Grouping되어 있으며, 우리는 Border에 Converter를 이용해서 색을 추가할 것이다.
Border 선택 -> BorderBrush 프로퍼티 오른쪽 네모 클릭 -> Data Binding -> BusCurrent 확장 -> RouteType 선택
-> Value converter 프로퍼티 오른쪽 Combobox -> RouteTypeToColorConverter 선택 -> OK
 


위와 같이 선택하면 Border의 테두리 색이 BusCurrent 프로퍼티의 RouteType에 따라서 변경이 된다.

이제 나머지 TextBlock로 각각 바인딩을 해 보자
1) 3100-1의정부 TextBlock 선택 -> Text 프로퍼티 오른쪽 네모 클릭 -> Data Binding -> BusCurrent 확장 -> BusRouteNm 선택 -> OK
2) 지선 TextBlock 선택 -> Text 프로퍼티 오른쪽 네모 클릭 -> Data Binding -> BusCurrent 확장 -> RouteType 선택
-> Value converter 오른쪽 Combobox -> RouteTypeToNameConverter 선택 -> OK
3) 내유동종점 TextBlock 선택 -> Text 프로퍼티 오른쪽 네모 클릭 -> Data Binding -> BusCurrent 확장 -> StartStationNm 선택 -> OK
4) 영등포소방서,타임스퀘어 TextBlock 선택 -> Text 프로퍼티 오른쪽 네모 클릭 -> Data Binding -> BusCurrent 확장 -> EndStationNm 선택 -> OK
** 전체적으로 화면 디자인을 변경하였기 때문에 MainPage.xaml, StationByRouteView.xaml, BusInfoResourceDictionary.xaml 파일의 전체 소스를 강좌 하단에 추가하도록 하겠다.
실행 결과를 확인해 보자
 

2. 운행 정보 추가하기
노선버스위치 정보 목록조회(http://api.bus.go.kr/contents/sub02/getBusPosByRtid.html) 서비스를 이용해서 운행 정보에 표시를 하면 된다. 그러면, 웹 페이지의 정보와 실제 반환 되는 결과를 가지고 모델을 만들어 보자.
(참고로 바인딩을 하지 않는 프로퍼티는 간단하게 줄여서 써도 상관없다.)

Models 폴더 -> Add -> Class -> name : BusPosModel.cs -> OK

BusPosModel.cs
using System.ComponentModel;
using System;

namespace BusInfo.Models
{
    //노선버스위치 정보 목록조회 http://api.bus.go.kr/contents/sub02/getBusPosByRtid.html
    //<busType>0</busType>
    //<dataTm>20111223181759</dataTm>
    //<gpsX>127.16104980422071</gpsX>
    //<gpsY>37.85877293616452</gpsY>
    //<lastStTm>15685</lastStTm>
    //<lastStnId>57431</lastStnId>
    //<nextStTm>0</nextStTm>
    //<plainNo>경기72바6196</plainNo>
    //<sectDist>1211</sectDist>
    //<sectOrd>3</sectOrd>
    //<sectionId>90011557</sectionId>
    //<stopFlag>1</stopFlag>
    //<vehId>12805</vehId>
    //<fullSectDist>1930</fullSectDist>
    //<trnstnid>6330</trnstnid>

    public class BusPosModel : INotifyPropertyChanged
    {
        #region PropertyChange
        public event PropertyChangedEventHandler PropertyChanged;

        private void FirePropertyChange(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        int sectOrd;
        /// <summary>
        /// 구간순번
        /// </summary>
        public int SectOrd
        {
            get { return sectOrd; }
            set
            {
                sectOrd = value;
                FirePropertyChange("SectOrd");
            }
        }

        float fullSectDist;
        /// <summary>
        /// 정류소간 거리
        /// </summary>
        public float FullSectDist
        {
            get { return fullSectDist; }
            set
            {
                fullSectDist = value;
                FirePropertyChange("FullSectDist");
            }
        }

        int sectDist;
        /// <summary>
        /// 구각옵셋거리Km
        /// </summary>
        public int SectDist
        {
            get { return sectDist; }
            set
            {
                sectDist = value;
                FirePropertyChange("SectDist");
            }
        }

        bool stopFlag;
        /// <summary>
        /// 정류소도착여부 (0:운행중, 1:도착)
        /// </summary>
        public bool StopFlag
        {
            get { return stopFlag; }
            set
            {
                stopFlag = value;
                FirePropertyChange("StopFlag");
            }
        }

        int sectionId;
        /// <summary>
        /// 구간ID
        /// </summary>
        public int SectionId
        {
            get { return trnstnId; }
            set
            {
                trnstnId = value;
                FirePropertyChange("TrnstnId");
            }
        }

        DateTime dataTm;
        /// <summary>
        /// 제공시간
        /// </summary>
        public DateTime DataTm
        {
            get { return dataTm; }
            set
            {
                dataTm = value;
                FirePropertyChange("DataTm");
            }
        }

        double gpsX;
        /// <summary>
        /// X좌표 WGS 84
        /// </summary>
        public double GpsX
        {
            get { return gpsX; }
            set
            {
                gpsX = value;
                FirePropertyChange("GpsX");
            }
        }

        double gpsY;
        /// <summary>
        /// Y좌표 WGS 84
        /// </summary>
        public double GpsY
        {
            get { return gpsY; }
            set
            {
                gpsY = value;
                FirePropertyChange("GpsY");
            }
        }

        int vehId;
        /// <summary>
        /// 버스 ID
        /// </summary>
        public int VehId
        {
            get { return vehId; }
            set
            {
                vehId = value;
                FirePropertyChange("VehId");
            }
        }

        string plainNo;
        /// <summary>
        /// 차량번호
        /// </summary>
        public string PlainNo
        {
            get { return plainNo; }
            set
            {
                plainNo = value;
                FirePropertyChange("PlainNo");
            }
        }

        int busType;
        /// <summary>
        /// 차량유형 (0:일반버스, 1:저상버스, 2:굴절버스)
        /// </summary>
        public int BusType
        {
            get { return busType; }
            set
            {
                busType = value;
                FirePropertyChange("BusType");
            }
        }

        int lastStTm;
        /// <summary>
        /// 종점도착소요시간
        /// </summary>
        public int LastStTm
        {
            get { return lastStTm; }
            set
            {
                lastStTm = value;
                FirePropertyChange("LastStTm");
            }
        }

        int lastStnId;
        /// <summary>
        /// 종점정류소ID
        /// </summary>
        public int LastStnId
        {
            get { return lastStnId; }
            set
            {
                lastStnId = value;
                FirePropertyChange("LastStnId");
            }
        }

        int nextStTm;
        /// <summary>
        /// 다음정류소도착소요시간
        /// </summary>
        public int NextStTm
        {
            get { return nextStTm; }
            set
            {
                nextStTm = value;
                FirePropertyChange("NextStTm");
            }
        }

        int trnstnId;
        /// <summary>
        /// 회차지정류소ID
        /// </summary>
        public int TrnstnId
        {
            get { return trnstnId; }
            set
            {
                trnstnId = value;
                FirePropertyChange("TrnstnId");
            }
        }
    }
}

모델이 다 만들어 졌으면, 이제 노선버스 위치 정보 조회 서비스를 호출해서 데이터를 넣어야 하는데.. 호출 위치는 정류소 목록의 조회 처리가 완료 된 후에 하는 것으로 하겠다. 왜냐하면, 기본적으로 WebClient는 한번에 하나의 처리만 가능하고, 정류소 목록이 없으면, 노선버스 위치 정보 데이터도 의미가 없기 때문이다.

MainPageViewModel.cs에 추가

ObservableCollection<BusPosModel> busPosCollection;
/// <summary>
/// 버스 위치 정보 컬렉션
/// </summary>
public ObservableCollection<BusPosModel> BusPosCollection
{
    get { return busPosCollection; }
    set
    {
        busPosCollection = value;
        FirePropertyChange("BusPosCollection");
    }
}


StationCollection = new ObservableCollection<StationByRouteModel>(stations); //<-- 소스가 있는 위치를 찾아서 그 아래 추가

//노선버스 위치 정보 조회
var uri = new Uri("http://ws.bus.go.kr/api/rest/buspos/getBusPosByRtid?ServiceKey=" + SKey.BusRouteInfo + "&busRouteId=" + BusCurrent.BusRouteId);
wc.DownloadStringAsync(uri, "GetBusPosByRtid");

//노선버스 위치 정보 파싱
case "GetBusPosByRtid":
 var busposs = from item in xml.Descendants("itemList")
              let dt = item.Element("dataTm").Value
              let year = dt.Substring(0, 4) + "-"
              let month = dt.Substring(4, 2) + "-"
              let day = dt.Substring(6, 2) + " "
              let hour = dt.Substring(8, 2) + ":"
              let min = dt.Substring(10, 2) + ":"
              let sec = dt.Substring(12, 2)
              select new BusPosModel
              {
                  BusType = Convert.ToInt32(item.Element("busType").Value),
                  DataTm = DateTime.Parse(year + month + day + hour + min + sec),
                  GpsX = Convert.ToDouble(item.Element("gpsX").Value),
                  GpsY = Convert.ToDouble(item.Element("gpsY").Value),
                  LastStTm = Convert.ToInt32(item.Element("lastStTm").Value),
                  LastStnId = Convert.ToInt32(item.Element("lastStnId").Value),
                  NextStTm = Convert.ToInt32(item.Element("nextStTm").Value),
                  PlainNo = item.Element("plainNo").Value,
                  SectDist = Convert.ToInt32(item.Element("sectDist").Value),
                  SectOrd = Convert.ToInt32(item.Element("sectOrd").Value),
                  SectionId = Convert.ToInt32(item.Element("sectionId").Value),
                  StopFlag = item.Element("stopFlag").Value == "1" ? true : false,
                  VehId = Convert.ToInt32(item.Element("vehId").Value),
                  FullSectDist = Convert.ToSingle(item.Element("fullSectDist").Value),
                  TrnstnId = Convert.ToInt32(item.Element("trnstnid").Value)
              };
BusPosCollection = new ObservableCollection<BusPosModel>(busposs);
break;

BusPosCollection에 데이터가 잘 들어오는지 확인 해보자.

지금부터 생각할 문제가 있는데, 우리는 정류소 정보와 위치 정보 2개의 데이터들을 조회해서 컬렉션에 담아 두었다. 그럼 이 2개의 데이터들을 어떻게 하나로 합쳐서 ListBox에 뿌려주고, 어떻게 바인딩을 할 것인가 라는 문제가 생긴다. ListBox에 ItemsSource는 1개의 프로퍼티만 바인딩이 가능하기 때문이다. 혹시, 2개가 바인딩이 된다고 하더라도 2개의 컬렉션들을 연결시키는 방법에 대한 문제가 남는다.

위의 문제를 해결하는 방법은 2개의 데이터를 하나로 묶어 주는 모델을 추가하고, 그 모델의 컬렉션 데이터를 만든 후 ListBox에 바인딩을 하면 깔끔하게 처리가 된다.

3. 연결 모델 추가

Models 폴더에 StationBusPosRow.cs 를 추가한다.

StationBusPosRow.cs

using System.ComponentModel;

namespace BusInfo.Models
{
    public class StationBusPosRow : INotifyPropertyChanged
    {
        #region PropertyChange
        public event PropertyChangedEventHandler PropertyChanged;

        private void FirePropertyChange(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        StationByRouteModel station;
        /// <summary>
        /// 정류소 정보
        /// </summary>
        public StationByRouteModel Station
        {
            get { return station; }
            set
            {
                station = value;
                FirePropertyChange("Station");
            }
        }

        BusPosModel busPos;
        /// <summary>
        /// 버스 위치 정보
        /// </summary>
        public BusPosModel BusPos
        {
            get { return busPos; }
            set
            {
                busPos = value;
                FirePropertyChange("BusPos");
            }
        }

    }
}

모델을 담을 컬렉션을 MainPageViewModel.cs에 추가

ObservableCollection<StationBusPosRow> rowCollection;
/// <summary>
/// 정류소 목록과 버스 위치 정보
/// </summary>
public ObservableCollection<StationBusPosRow> RowCollection
{
    get { return rowCollection; }
    set
    {
        rowCollection = value;
        FirePropertyChange("RowCollection");
    }
}

아까 추가했던 위치 정보 파싱하는 곳의 하단에 합치는 코드를 추가한다.

//2개의 컬렉션을 1개로 합치기 left join
var SBProws = from station in StationCollection
              join pos in BusPosCollection
              on station.Station equals pos.LastStnId into joiner
              from row in joiner.DefaultIfEmpty()
              select new StationBusPosRow
              {
                  Station = station,
                  BusPos = row
              };
RowCollection = new ObservableCollection<StationBusPosRow>(SBProws);

F6을 눌러서 빌드한 후 Blend로 이동해서 ListBox의 ItemsSource를 RowCollection으로 변경하고, StationDataTemplate에 Binding되어 있던 데이터들도 수정하고, 운행정보 TextBlock, 차량번호 TextBlock의 Text 프로퍼티에도 Binding을 추가한다. 아래는 수정된 템플릿 소스이다.

<DataTemplate x:Key="StationDataTemplate">
 <Border BorderThickness="0" Width="450">
  <Grid Height="64" >
   <Grid.ColumnDefinitions>
    <ColumnDefinition Width="0.8*"/>
    <ColumnDefinition Width="0.2*"/>
   </Grid.ColumnDefinitions>
   <Grid.RowDefinitions>
    <RowDefinition Height="0.5*"/>
    <RowDefinition Height="0.5*"/>
   </Grid.RowDefinitions>
   <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
    <TextBlock TextWrapping="Wrap" Text="{Binding Station.Seq}" Foreground="Gray"/>
    <TextBlock TextWrapping="Wrap" Text="{Binding Station.StationNo}" Margin="20,0,0,0" Foreground="Gray"/>
   </StackPanel>
   <TextBlock Grid.Column="1" TextWrapping="Wrap" Text="{Binding BusPos.StopFlag}" VerticalAlignment="Bottom" Foreground="White"/>
   <StackPanel Orientation="Horizontal" Grid.Row="1" VerticalAlignment="Top">
    <TextBlock TextWrapping="Wrap" Text="{Binding Station.StationNm}"/>
   </StackPanel>
   <TextBlock Grid.Column="1" TextWrapping="Wrap" Text="{Binding BusPos.PlainNo}" Grid.Row="1" FontSize="13.333" VerticalAlignment="Top" Foreground="Gray"/>
  </Grid>
 </Border>
</DataTemplate>

실행해서 결과를 확인 하자.

이제 마지막으로 true, false를 택스트로 변경할 Converter를 하나 추가하여 깔끔하게 마무리를 지어보자

4. StopFlagToTextConverter 추가

using System;
using System.Windows.Data;

namespace BusInfo.Converters
{
    public class StopFlagToTextConverter : IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool flag = System.Convert.ToBoolean(value);
            string returnValue = string.Empty;

            if (flag == true)
                returnValue = "도착";
            else
                returnValue = "운행중";

            return returnValue;
           
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

빌드 후 Blend에서 작업을 해서 추가하고, 실행해서 결과를 다시 확인 한다.

5. 위치 재조회 기능
버스 번호를 Button으로 변경하고 버튼을 누르면 위치 정보를 다시 조회해서 화면에 출력하도록 만들면 된다. 그런데, 여기에 한가지 문제점이 숨겨져 있다. 문제는 다시 DownloadStringAsync을 호출한다고 해도 동일한 결과만을 반환한다는 것이다.. 그 문제의 해결은 본인이 스스로 해보기 바란다.(구글을 통해서 찾으면 답이 있다)

6. 지금까지 작업한 최종 화면을 살펴 보자



7. 소스..

MainPage.xaml

<phone:PhoneApplicationPage
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:BusInfo_ViewModels="clr-namespace:BusInfo.ViewModels"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:BusInfo_Converters="clr-namespace:BusInfo.Converters"
    x:Class="BusInfo.MainPage"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">
 
 <phone:PhoneApplicationPage.Resources>
  <BusInfo_Converters:BoolToBoolRevConvert x:Key="BoolToBoolRevConvert"/>
 </phone:PhoneApplicationPage.Resources>
 <i:Interaction.Triggers>
  <i:EventTrigger>
   <i:InvokeCommandAction Command="{Binding LoadCommand, Mode=OneWay}"/>
  </i:EventTrigger>
 </i:Interaction.Triggers>
 
    <!--Sample code showing usage of ApplicationBar-->
    <!--<phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1"/>
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
                <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>-->

 <phone:PhoneApplicationPage.FontFamily>
  <StaticResource ResourceKey="PhoneFontFamilyNormal"/>
 </phone:PhoneApplicationPage.FontFamily>
 <phone:PhoneApplicationPage.FontSize>
  <StaticResource ResourceKey="PhoneFontSizeNormal"/>
 </phone:PhoneApplicationPage.FontSize>
 <phone:PhoneApplicationPage.Foreground>
  <StaticResource ResourceKey="PhoneForegroundBrush"/>
 </phone:PhoneApplicationPage.Foreground>

 <d:DataContext>
  <BusInfo_ViewModels:MainPageViewModel/>
 </d:DataContext>

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="kaki104.tistory.com App" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="버스 목록 조회" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}" FontSize="48" />
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
         <Grid.RowDefinitions>
          <RowDefinition Height="72"/>
          <RowDefinition/>
         </Grid.RowDefinitions>
         <StackPanel Orientation="Horizontal">
          <TextBlock TextWrapping="Wrap" Text="노선번호" VerticalAlignment="Center" Width="80"/>
          <TextBox TextWrapping="Wrap" Text="{Binding SBusNum, Mode=TwoWay}" Width="267" InputScope="Number"/>
          <Button Content="조회" IsEnabled="{Binding IsBusy, Converter={StaticResource BoolToBoolRevConvert}, Mode=OneWay}">
           <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
             <i:InvokeCommandAction Command="{Binding GetBusRouteListCommand, Mode=OneWay}"/>
            </i:EventTrigger>
           </i:Interaction.Triggers>
          </Button>
         </StackPanel>
         <ListBox Grid.Row="1" x:Name="listBox" ItemTemplate="{StaticResource BusRouteDataTemplate}" ItemsSource="{Binding BusRouteCollection}">
          <i:Interaction.Triggers>
           <i:EventTrigger EventName="SelectionChanged">
            <i:InvokeCommandAction Command="{Binding SelectionChangedCommand, Mode=OneWay}" CommandParameter="{Binding SelectedItem, ElementName=listBox}"/>
           </i:EventTrigger>
          </i:Interaction.Triggers>
         </ListBox>
        </Grid>
    </Grid>

</phone:PhoneApplicationPage>


StationbyRouteView.xaml

<phone:PhoneApplicationPage
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:BusInfo_ViewModels="clr-namespace:BusInfo.ViewModels"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    x:Class="BusInfo.Views.StationByRouteView"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
    shell:SystemTray.IsVisible="True">
 
    <!--Sample code showing usage of ApplicationBar-->
    <!--<phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1"/>
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
                <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>-->

 <d:DataContext>
  <BusInfo_ViewModels:MainPageViewModel/>
 </d:DataContext>

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="kaki104.tistory.com App" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="경유정류소 &amp; 위치" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}" FontSize="48"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
         <Grid.RowDefinitions>
          <RowDefinition Height="0.131*"/>
          <RowDefinition Height="0.869*"/>
         </Grid.RowDefinitions>
         <ListBox Grid.Row="1" ItemsSource="{Binding RowCollection}" ItemTemplate="{StaticResource StationDataTemplate}" Margin="0,-1,0,0"/>
         <Grid>
          <Grid.RowDefinitions>
           <RowDefinition Height="0.463*"/>
           <RowDefinition Height="0.537*"/>
          </Grid.RowDefinitions>
          <Grid.ColumnDefinitions>
           <ColumnDefinition Width="0.409*"/>
           <ColumnDefinition Width="0.591*"/>
          </Grid.ColumnDefinitions>
          <Button HorizontalAlignment="Center" Grid.RowSpan="2" VerticalAlignment="Center" Content="{Binding BusCurrent.BusRouteNm}" Background="{Binding BusCurrent.RouteType, Converter={StaticResource RouteTypeToColorConverter}}">
           <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
             <i:InvokeCommandAction Command="{Binding RefreshCommand, Mode=OneWay}"/>
            </i:EventTrigger>
           </i:Interaction.Triggers>
          </Button>
          <TextBlock Grid.Column="1" TextWrapping="Wrap" Text="{Binding BusCurrent.RouteType, Converter={StaticResource RouteTypeToNameConverter}}" Foreground="Gray" VerticalAlignment="Bottom"/>
          <StackPanel Grid.Column="1" Orientation="Horizontal" Grid.Row="1" d:LayoutOverrides="Height">
           <TextBlock TextWrapping="Wrap" Text="{Binding BusCurrent.StartStationNm}" Foreground="Gray" Height="45" VerticalAlignment="Top"/>
           <TextBlock TextWrapping="Wrap" Text="~" Foreground="Gray" Height="45" VerticalAlignment="Top"/>
           <TextBlock TextWrapping="Wrap" Text="{Binding BusCurrent.EndStationNm}" Foreground="Gray" Height="45" VerticalAlignment="Top"/>
          </StackPanel>
         </Grid>
        </Grid>
    </Grid>

</phone:PhoneApplicationPage>


BusInfoResourceDictionary.xaml

<ResourceDictionary
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:BusInfo_Converters="clr-namespace:BusInfo.Converters">
 <BusInfo_Converters:StopFlagToTextConverter x:Key="StopFlagToTextConverter"/>
 <BusInfo_Converters:RouteTypeToColorConverter x:Key="RouteTypeToColorConverter"/>
 <!-- Resource dictionary entries should be defined here. -->
 <BusInfo_Converters:RouteTypeToNameConverter x:Key="RouteTypeToNameConverter"/>
 
 <DataTemplate x:Key="BusRouteDataTemplate">
  <Grid Width="450" Height="64">
   <Grid.RowDefinitions>
    <RowDefinition Height="0.5*"/>
    <RowDefinition Height="0.5*"/>
   </Grid.RowDefinitions>
   <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
    <TextBlock TextWrapping="Wrap" Text="{Binding RouteType, Converter={StaticResource RouteTypeToNameConverter}}"/>
    <Border BorderThickness="10,0,0,0" BorderBrush="{Binding RouteType, Converter={StaticResource RouteTypeToColorConverter}}" Margin="20,0,0,0">
     <TextBlock TextWrapping="Wrap" Text="{Binding BusRouteNm}" Height="27" />
    </Border>
   </StackPanel>
   <StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Top">
    <TextBlock Text="{Binding StartStationNm}" Foreground="Gray" FontSize="18.667"/>
    <TextBlock TextWrapping="Wrap" Text=" ~ " Foreground="Gray" FontSize="18.667"/>
    <TextBlock Text="{Binding EndStationNm}" Foreground="Gray" FontSize="18.667"/>
   </StackPanel>
  </Grid>
 </DataTemplate>
 <DataTemplate x:Key="StationDataTemplate">
  <Border BorderThickness="0" Width="450">
   <Grid Height="64" >
    <Grid.ColumnDefinitions>
     <ColumnDefinition Width="0.8*"/>
     <ColumnDefinition Width="0.2*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
     <RowDefinition Height="0.5*"/>
     <RowDefinition Height="0.5*"/>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
     <TextBlock TextWrapping="Wrap" Text="{Binding Station.Seq}" Foreground="Gray"/>
     <TextBlock TextWrapping="Wrap" Text="{Binding Station.StationNo}" Margin="20,0,0,0" Foreground="Gray"/>
    </StackPanel>
    <TextBlock Grid.Column="1" TextWrapping="Wrap" Text="{Binding BusPos.StopFlag, Converter={StaticResource StopFlagToTextConverter}}" VerticalAlignment="Bottom" Foreground="White"/>
    <StackPanel Orientation="Horizontal" Grid.Row="1" VerticalAlignment="Top">
     <TextBlock TextWrapping="Wrap" Text="{Binding Station.StationNm}"/>
    </StackPanel>
    <TextBlock Grid.Column="1" TextWrapping="Wrap" Text="{Binding BusPos.PlainNo}" Grid.Row="1" FontSize="13.333" VerticalAlignment="Top" Foreground="Gray"/>
   </Grid>
  </Border>
 </DataTemplate>
</ResourceDictionary>

 

 

MainPageViewModel.cs

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Input;
using System.Xml.Linq;
using BusInfo.Functions;
using BusInfo.Models;
using Microsoft.Expression.Interactivity.Core;
using Microsoft.Phone.Controls;

namespace BusInfo.ViewModels
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        #region PropertyChange
        public event PropertyChangedEventHandler PropertyChanged;

        private void FirePropertyChange(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        /// <summary>
        /// 검색할 버스 노선 번호
        /// </summary>
        public string SBusNum { get; set; }

        /// <summary>
        /// 웹 클라이언트
        /// </summary>
        WebClient wc;

        bool isBusy;
        /// <summary>
        /// 웹 클라이언트 조회 중 여부
        /// </summary>
        public bool IsBusy
        {
            get { return isBusy; }
            private set
            {
                isBusy = value;
                FirePropertyChange("IsBusy");
            }
        }

        /// <summary>
        /// 인증 키
        /// </summary>
        ServiceKey SKey;

        ObservableCollection<BusRouteModel> busRouteCollection;
        /// <summary>
        /// 버스 목록 컬렉션 - 프로퍼티
        /// </summary>
        public ObservableCollection<BusRouteModel> BusRouteCollection
        {
            get { return busRouteCollection; }
            set
            {
                busRouteCollection = value;
                FirePropertyChange("BusRouteCollection");
            }
        }

        BusRouteModel busCurrent;
        /// <summary>
        /// 선택된 버스 정보 프로퍼티
        /// </summary>
        public BusRouteModel BusCurrent
        {
            get { return busCurrent; }
            set
            {
                busCurrent = value;
                FirePropertyChange("BusCurrent");
            }
        }

        ObservableCollection<StationByRouteModel> stationCollection;
        /// <summary>
        /// 노선별 경유 정류소 컬렉션 - 프로퍼티
        /// </summary>
        public ObservableCollection<StationByRouteModel> StationCollection
        {
            get { return stationCollection; }
            set
            {
                stationCollection = value;
                FirePropertyChange("StationCollection");
            }
        }

        ObservableCollection<BusPosModel> busPosCollection;
        /// <summary>
        /// 버스 위치 정보
        /// </summary>
        public ObservableCollection<BusPosModel> BusPosCollection
        {
            get { return busPosCollection; }
            set
            {
                busPosCollection = value;
                FirePropertyChange("BusPosCollection");
            }
        }

        ObservableCollection<StationBusPosRow> rowCollection;
        /// <summary>
        /// 정류소 목록과 버스 위치 정보
        /// </summary>
        public ObservableCollection<StationBusPosRow> RowCollection
        {
            get { return rowCollection; }
            set
            {
                rowCollection = value;
                FirePropertyChange("RowCollection");
            }
        }

        //메인 프레임
        PhoneApplicationFrame root;

        /// <summary>
        /// 생성자
        /// </summary>
        public MainPageViewModel()
        {
            //웹클라이언트 생성
            wc = new WebClient();
            wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
            //서비스키 생성
            SKey = new ServiceKey();
        }

        /// <summary>
        /// 웹클라이언트 다운로드스트링 컴플릿트 이벤트 구현
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {

            IsBusy = wc.IsBusy;

            if (e.Error != null)
                return;
            //xml을 xElement라는 객체로 바로 파싱해서 사용한다.
            XElement xml = XElement.Parse(e.Result);
            //어떤 서비스의 결과인지에 따라서 각각 다른 처리를 한다.
            switch (e.UserState as string)
            {
                case "GetBusRouteList":
                    //버스 노선 목록 파싱
                    var rows = from item in xml.Descendants("itemList")
                               select new BusRouteModel
                               {
                                   BusRouteId = Convert.ToInt32(item.Element("busRouteId").Value),
                                   BusRouteNm = item.Element("busRouteNm").Value,
                                   StartStationNm = item.Element("stStationNm").Value,
                                   EndStationNm = item.Element("edStationNm").Value,
                                   RouteType = Convert.ToInt32(item.Element("routeType").Value),
                                   Term = Convert.ToInt32(item.Element("term").Value)
                               };
                    BusRouteCollection = new ObservableCollection<BusRouteModel>(rows);
                    break;
                case "GetStaionByRoute":
                    //정류소 목록 파싱
                    var stations = from item in xml.Descendants("itemList")
                                   select new StationByRouteModel
                                   {
                                       BusRouteId = Convert.ToInt32(item.Element("busRouteId").Value),
                                       BusRouteNm = item.Element("busRouteNm").Value,
                                       Seq = Convert.ToInt32(item.Element("seq").Value),
                                       Section = Convert.ToInt32(item.Element("section").Value),
                                       Station = Convert.ToInt32(item.Element("station").Value),
                                       StationNm = item.Element("stationNm").Value,
                                       GpsX = Convert.ToDouble(item.Element("gpsX").Value),
                                       GpsY = Convert.ToDouble(item.Element("gpsY").Value),
                                       FullSectDist = Convert.ToSingle(item.Element("fullSectDist").Value),
                                       StationNo = Convert.ToInt32(item.Element("stationNo").Value),
                                       RouteType = Convert.ToInt32(item.Element("routeType").Value),
                                       BeginTm = item.Element("beginTm").Value,
                                       LastTm = item.Element("lastTm").Value,
                                       TrnstnId = Convert.ToInt32(item.Element("trnstnid").Value)
                                   };
                    StationCollection = new ObservableCollection<StationByRouteModel>(stations);

                    //노선버스 위치 정보 조회
                    var uri = new Uri("http://ws.bus.go.kr/api/rest/buspos/getBusPosByRtid?ServiceKey=" + SKey.BusRouteInfo + "&busRouteId=" + BusCurrent.BusRouteId);
                    wc.DownloadStringAsync(uri, "GetBusPosByRtid");
                    break;
                case "GetBusPosByRtid":
                    //버스 운행 정보 파싱
                    var busposs = from item in xml.Descendants("itemList")
                                  let dt = item.Element("dataTm").Value
                                  let year = dt.Substring(0, 4) + "-"
                                  let month = dt.Substring(4, 2) + "-"
                                  let day = dt.Substring(6, 2) + " "
                                  let hour = dt.Substring(8, 2) + ":"
                                  let min = dt.Substring(10, 2) + ":"
                                  let sec = dt.Substring(12, 2)
                                  select new BusPosModel
                                  {
                                      BusType = Convert.ToInt32(item.Element("busType").Value),
                                      DataTm = DateTime.Parse(year + month + day + hour + min + sec),
                                      GpsX = Convert.ToDouble(item.Element("gpsX").Value),
                                      GpsY = Convert.ToDouble(item.Element("gpsY").Value),
                                      LastStTm = Convert.ToInt32(item.Element("lastStTm").Value),
                                      LastStnId = Convert.ToInt32(item.Element("lastStnId").Value),
                                      NextStTm = Convert.ToInt32(item.Element("nextStTm").Value),
                                      PlainNo = item.Element("plainNo").Value,
                                      SectDist = Convert.ToInt32(item.Element("sectDist").Value),
                                      SectOrd = Convert.ToInt32(item.Element("sectOrd").Value),
                                      SectionId = Convert.ToInt32(item.Element("sectionId").Value),
                                      StopFlag = item.Element("stopFlag").Value == "1" ? true : false,
                                      VehId = Convert.ToInt32(item.Element("vehId").Value),
                                      FullSectDist = Convert.ToSingle(item.Element("fullSectDist").Value),
                                      TrnstnId = Convert.ToInt32(item.Element("trnstnid").Value)
                                  };
                    BusPosCollection = new ObservableCollection<BusPosModel>(busposs);


                    //2개의 컬렉션을 1개로 합치기 left join
                    var SBProws = from station in StationCollection
                                  join pos in BusPosCollection
                                  on station.Station equals pos.LastStnId into joiner
                                  from row in joiner.DefaultIfEmpty()
                                  select new StationBusPosRow
                                  {
                                      Station = station,
                                      BusPos = row
                                  };
                    RowCollection = new ObservableCollection<StationBusPosRow>(SBProws);
                    break;
            }

            if (1 == 1)
            {
            }
        }

        private ICommand getBusRouteListCommand;
        /// <summary>
        /// 노선 목록 조회 커맨드
        /// </summary>
        public ICommand GetBusRouteListCommand
        {
            get
            {
                if (getBusRouteListCommand == null)
                {
                    getBusRouteListCommand = new ActionCommand(() =>
                    {
                        if (SBusNum == null || SBusNum.Length <= 1)
                        {
                            MessageBox.Show("2글자 이상 입력해야 합니다.");
                            return;
                        }
                        //REST 형식 OpenAPI 호출
                        var uri = new Uri("http://ws.bus.go.kr/api/rest/busRouteInfo/getBusRouteList?ServiceKey=" + SKey.BusRouteInfo + "&strSrch=" + SBusNum);
                        //입력데이터 인코딩
                        SBusNum = Uri.EscapeUriString(SBusNum);
                        //비동기 호출, 호출 구분자로 GetBusRouteList를 사용함
                        wc.DownloadStringAsync(uri, "GetBusRouteList");

                        IsBusy = wc.IsBusy;
                    });
                }
                return getBusRouteListCommand;
            }
        }

        private ICommand selectionChangedCommand;
        /// <summary>
        /// 노선 목록에서 선택된 아이템이 변경된 경우 실행되는 커맨드
        /// </summary>
        public ICommand SelectionChangedCommand
        {
            get
            {
                if (selectionChangedCommand == null)
                {
                    selectionChangedCommand = new ActionCommand(item =>
                    {
                        //커맨드 파라메터로 전달 받은 오브젝트를 형변환
                        BusRouteModel route = item as BusRouteModel;
                        //형변환을 성공적으로 처리했다면
                        if (route != null)
                        {
                            //선택된 버스 정보 저장
                            BusCurrent = route;

                            //정류소 조회
                            var uri = new Uri("http://ws.bus.go.kr/api/rest/busRouteInfo/getStaionByRoute?ServiceKey=" + SKey.BusRouteInfo + "&busRouteId=" + route.BusRouteId);
                            wc.DownloadStringAsync(uri, "GetStaionByRoute");

                            //던지고 바로 정류소 목록 조회 화면으로 네비게이션
                            root.Navigate(new Uri("/Views/StationByRouteView.xaml", UriKind.Relative));
                        }
                    });
                }
                return selectionChangedCommand;
            }
        }

        private ICommand loadCommand;
        /// <summary>
        /// MainPage.xaml이 로드가 될때 실행
        /// </summary>
        public ICommand LoadCommand
        {
            get
            {
                if (loadCommand == null)
                {
                    loadCommand = new ActionCommand(() =>
                    {
                        //메인 프레임 입력 - 네비게이션하는데 필요
                        if (root == null)
                        {
                            //생성자에서는 이 데이터를 가지고 올 수가 없음..
                            root = App.Current.RootVisual as PhoneApplicationFrame;
                        }
                        //격리 저장소에 뷰모델을 저장
                        StaticFunctions.SaveToIS("MainPageViewModel", this);
                    });
                }
                return loadCommand;
            }
        }

        private ICommand refreshCommand;
        /// <summary>
        /// 위치 정보만 다시 조회
        /// </summary>
        public ICommand RefreshCommand
        {
            get
            {
                if (refreshCommand == null)
                {
                    refreshCommand = new ActionCommand(() =>
                    {
                        //노선버스 위치 정보 조회
                        var uri = new Uri("http://ws.bus.go.kr/api/rest/buspos/getBusPosByRtid?ServiceKey=" + SKey.BusRouteInfo + "&busRouteId=" + BusCurrent.BusRouteId);
                       
                        wc.DownloadStringAsync(uri, "GetBusPosByRtid");
                    });
                }
                return refreshCommand;
            }
        }
    }
}

8. 이제 기본 기능은 
완성이 된것 같다. 나머지는 즐겨 찾기 기능을 추가하는 정도만 하면 될 것 같다. 이제는 당장 사용해도 문제는 없으니 혹시 xap파일이 필요한 분은 리플로 요청을 하면 메일로 발송 하도록 하겠다.(물론 unlock이 되어야지만 실행이 가능하다.)
내일 부터는 버스가 언제 올지 궁금해 하지 않아도 되겠다. 하하하

'Windows Phone 8' 카테고리의 다른 글

Seoul Bus Info Search App Dev 8  (0) 2012.01.08
Posted by MVP kaki104

윈도우 폰에 강좌 진행 중이던 엡(BusInfo)을 배포했다. Emulator만 거의 1년을 보다가 진짜 폰에 배포를 해보니 감동이...
2010년에 부터 윈도우 폰 프로그램을 하기 위해서 Silverlight와 그 외의 기술을 배우던 나로서는 정말 말로 형용할 수가 없다.

사진은 이전에 사용하던 HTC Diamond로 촬영한 것이다

1. xap 파일을 배포하는 것은 윈도우 폰 개발자 등록을 마친 사용자의 폰에만 가능하다.(다른 경로도 있지만..알지 못한다.)


위의 문서를 참고하기 바란다.
개발자 등록을 마친 후 unlock하는 방법
http://msdn.microsoft.com/en-us/library/ff769508(v=VS.92).aspx

2. 오늘은 혼자 조용히 자축을 해야 할 것 같다. ^^ 8번 강좌는 내일 작성 해서 올리도록 하겠다.

'Windows Phone 8 > Korea Bus Information' 카테고리의 다른 글

Seoul Bus Info Search App Dev 10  (0) 2012.01.13
Seoul Bus Info Search App Dev 9  (0) 2012.01.12
My BusInfo app deploy  (0) 2012.01.08
Seoul Bus Info Search App Dev 7  (0) 2012.01.07
Seoul Bus Info Search App Dev 6  (0) 2012.01.07
Seoul Bus Info Search App Dev 5  (0) 2012.01.07
Posted by MVP kaki104

버스 정보 검색 엡 만들기 강좌를 빨리 마무리를 하기 위해 속도를 올리고 있는데… 따라 하기는 잘 진행이 되고 있는 지 궁금하다.
이렇게 이야기를 해도 반응은 거의 없으니..후딱 시작 해야겠다.

1. StationDataTemplate 만들기
처음에 BusRouteDataTemplate 만드는 방법을 참고해서 StationDataTemplate 만들어 보자.


완성된 템플릿

<DataTemplate x:Key="StationDataTemplate">
 <Border BorderThickness="0" Width="450">
  <Grid >
   <Grid.ColumnDefinitions>
    <ColumnDefinition Width="0.8*"/>
    <ColumnDefinition Width="0.2*"/>
   </Grid.ColumnDefinitions>
   <Grid.RowDefinitions>
    <RowDefinition Height="0.5*"/>
    <RowDefinition Height="0.5*"/>
   </Grid.RowDefinitions>
   <StackPanel Orientation="Horizontal">
    <TextBlock TextWrapping="Wrap" Text="◎    순번 : "/>
    <TextBlock TextWrapping="Wrap" Text="{Binding Seq}"/>
    <TextBlock TextWrapping="Wrap" Text="◎ 정류장번호 : " Margin="10,0,0,0"/>
    <TextBlock TextWrapping="Wrap" Text="{Binding StationNo}"/>
   </StackPanel>
   <StackPanel Orientation="Horizontal" Grid.Row="1">
    <TextBlock TextWrapping="Wrap" Text="◎ 정류장 : "/>
    <TextBlock TextWrapping="Wrap" Text="{Binding StationNm}"/>
   </StackPanel>
   <TextBlock Grid.Column="1" TextWrapping="Wrap" Text="운행정보"/>
   <TextBlock Grid.Column="1" TextWrapping="Wrap" Text="차량번호" Grid.Row="1" FontSize="13.333" VerticalAlignment="Center"/>
  </Grid>
 </Border>
</DataTemplate>

실행해서 결과를 확인한다.


2. 중복 조회를 하지 못하게 조회 버튼 막기
WebClient를 이용해서 조회를 한번 실행 후 결과를 기다리는 동안 다시 조회를 실행하지 못하도록 BoolToBoolRevConvert를 하나 만들어서 처리하자.

using System;
using System.Windows.Data;

namespace BusInfo.Converters
{
    public class BoolToBoolRevConvert : IValueConverter
    {
        /// <summary>
        /// IsBusy 프로퍼티 데이터를 반대로 반환하는 컨버터
        /// IsBusy가 True이면 조회 중이기 때문에 조회 버튼의 IsEnabled를 False로 변경해 주어야 하고
        /// IsBusy가 False이면 조회를 할 수 있기 때문에 버튼의 Isenabled를 True로 변경해 주는 작업이 필요
        /// 이런 작업을 하기 위해 만든 컨버터
        /// </summary>
        /// <param name="value"></param>
        /// <param name="targetType"></param>
        /// <param name="parameter"></param>
        /// <param name="culture"></param>
        /// <returns></returns>
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool isbusy = System.Convert.ToBoolean(value);
            bool returnValue = false;

            if (isbusy == true)
                returnValue = false;
            else
                returnValue = true;

            return returnValue;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

MainPageViewModel.cs에서 IsBusy 프로퍼티를 추가한다.

/// <summary>
/// 웹 클라이언트
/// </summary>
WebClient wc;

bool isBusy;
/// <summary>
/// 웹 클라이언트 조회 중 여부
/// </summary>
public bool IsBusy
{
    get { return isBusy; }
    private set
    {
        isBusy = value;
        FirePropertyChange("IsBusy");
    }
}

GetBusRouteListCommand에서

//비동기 호출, 호출 구분자로 GetBusRouteList를 사용함
wc.DownloadStringAsync(uri, "GetBusRouteList");

IsBusy = wc.IsBusy; //여기 하나 추가

void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)에서
IsBusy = wc.IsBusy; //맨 첫 번째 줄에 하나 추가

F6을 눌러 빌드 해준다. 이제 Blend로 이동한다.

 
조회 버튼 클릭 -> IsEnabled 프로퍼티 오른쪽에 네모 선택 -> Data Binding 선택
IsBusy 프로퍼티 선택 -> Value Converter 오른쪽에 […] 선택 -> BoolToBoolRevConvert 선택 -> OK

여기까지 확인 되었으면 OK 실행해서 결과를 확인하자.

조회 글씨가 회색으로 변한 것을 볼 수 있다.(조회가 완료되기 전까지는 조회 버튼과 작별이다)

3. 뷰 모델 저장하는 위치 수정
MainPageViewModel.cs의 생성자에 넣어 놓았던,

//격리 저장소에 뷰모델을 저장
StaticFunctions.SaveToIS("MainPageViewModel", this);

이 부분에 오류가 발생한다. 위치를 변경한다.

private ICommand loadCommand;
/// <summary>
/// 엡이 로드가 될때 실행
/// </summary>
public ICommand LoadCommand
{
    get
    {
        if (loadCommand == null)
        {
            loadCommand = new ActionCommand(() =>
            {
                //격리 저장소에 뷰모델을 저장
                StaticFunctions.SaveToIS("MainPageViewModel", this);
            });
        }
        return loadCommand;
    }
}

그리고 Blend에서 InvokeCommandAction을 PhoneApplicationPage에 추가 하고
EventName : Loaded -> Command : LoadCommand로 선택 해준다.
이렇게 하면 폼이 로드 될 때 한번 실행된다.

4. 1번에서 DataTemplate을 이용해서 경유정류소 목록을 조회해 봤는데..약간 부족한 점은 도대체 무슨 어떤 버스의 정류소 목록인지를 알 수가 없는 것과 운행정보, 차량 번호를 표시하지 못하는 것이다. 그 부분은 다음 강좌에서 처리하도록 하겠다.
추가로 약간의 수정 사항이 있는데 그것은 뷰 모델의 전체 소스를 참고 하기 바란다.

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Input;
using System.Xml.Linq;
using BusInfo.Functions;
using BusInfo.Models;
using Microsoft.Expression.Interactivity.Core;
using Microsoft.Phone.Controls;

namespace BusInfo.ViewModels
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        #region PropertyChange
        public event PropertyChangedEventHandler PropertyChanged;

        private void FirePropertyChange(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        /// <summary>
        /// 검색할 버스 노선 번호
        /// </summary>
        public string SBusNum { get; set; }

        /// <summary>
        /// 웹 클라이언트
        /// </summary>
        WebClient wc;

        bool isBusy;
        /// <summary>
        /// 웹 클라이언트 조회 중 여부
        /// </summary>
        public bool IsBusy
        {
            get { return isBusy; }
            private set
            {
                isBusy = value;
                FirePropertyChange("IsBusy");
            }
        }

        /// <summary>
        /// 인증 키
        /// </summary>
        ServiceKey SKey;

        ObservableCollection<BusRouteModel> busRouteCollection;
        /// <summary>
        /// 버스 목록 컬렉션 - 프로퍼티
        /// </summary>
        public ObservableCollection<BusRouteModel> BusRouteCollection
        {
            get { return busRouteCollection; }
            set
            {
                busRouteCollection = value;
                FirePropertyChange("BusRouteCollection");
            }
        }

        ObservableCollection<StationByRouteModel> stationCollection;
        /// <summary>
        /// 노선별 경유 정류소 컬렉션 - 프로퍼티
        /// </summary>
        public ObservableCollection<StationByRouteModel> StationCollection
        {
            get { return stationCollection; }
            set
            {
                stationCollection = value;
                FirePropertyChange("StationCollection");
            }
        }

        //메인 프레임
        PhoneApplicationFrame root;

        /// <summary>
        /// 생성자
        /// </summary>
        public MainPageViewModel()
        {
            //웹클라이언트 생성
            wc = new WebClient();
            wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
            //서비스키 생성
            SKey = new ServiceKey();
        }

        /// <summary>
        /// 웹클라이언트 다운로드스트링 컴플릿트 이벤트 구현
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {

            IsBusy = wc.IsBusy;

            if (e.Error != null)
                return;
            //xml을 xElement라는 객체로 바로 파싱해서 사용한다.
            XElement xml = XElement.Parse(e.Result);
            //어떤 서비스의 결과인지에 따라서 각각 다른 처리를 한다.
            switch (e.UserState as string)
            {
                case "GetBusRouteList":
                    //버스 노선 목록 파싱
                    var rows = from item in xml.Descendants("itemList")
                               select new BusRouteModel
                               {
                                   BusRouteId = Convert.ToInt32(item.Element("busRouteId").Value),
                                   BusRouteNm = item.Element("busRouteNm").Value,
                                   StartStationNm = item.Element("stStationNm").Value,
                                   EndStationNm = item.Element("edStationNm").Value,
                                   RouteType = Convert.ToInt32(item.Element("routeType").Value),
                                   Term = Convert.ToInt32(item.Element("term").Value)
                               };
                    BusRouteCollection = new ObservableCollection<BusRouteModel>(rows);
                    break;
                case "GetStaionByRoute":
                    //정류소 목록 파싱
                    var stations = from item in xml.Descendants("itemList")
                                   select new StationByRouteModel
                                   {
                                       BusRouteId = Convert.ToInt32(item.Element("busRouteId").Value),
                                       BusRouteNm = item.Element("busRouteNm").Value,
                                       Seq = Convert.ToInt32(item.Element("seq").Value),
                                       Section = Convert.ToInt32(item.Element("section").Value),
                                       Station = Convert.ToInt32(item.Element("station").Value),
                                       StationNm = item.Element("stationNm").Value,
                                       GpsX = Convert.ToDouble(item.Element("gpsX").Value),
                                       GpsY = Convert.ToDouble(item.Element("gpsY").Value),
                                       FullSectDist = Convert.ToSingle(item.Element("fullSectDist").Value),
                                       StationNo = Convert.ToInt32(item.Element("stationNo").Value),
                                       RouteType = Convert.ToInt32(item.Element("routeType").Value),
                                       BeginTm = item.Element("beginTm").Value,
                                       LastTm = item.Element("lastTm").Value,
                                       TrnstnId = Convert.ToInt32(item.Element("trnstnid").Value)
                                   };
                    StationCollection = new ObservableCollection<StationByRouteModel>(stations);
                    break;
            }

            if (1 == 1)
            {
            }
        }

        private ICommand getBusRouteListCommand;
        /// <summary>
        /// 노선 목록 조회 커맨드
        /// </summary>
        public ICommand GetBusRouteListCommand
        {
            get
            {
                if (getBusRouteListCommand == null)
                {
                    getBusRouteListCommand = new ActionCommand(() =>
                    {
                        if (SBusNum == null || SBusNum.Length <= 1)
                        {
                            MessageBox.Show("2글자 이상 입력해야 합니다.");
                            return;
                        }
                        //REST 형식 OpenAPI 호출
                        var uri = new Uri("http://ws.bus.go.kr/api/rest/busRouteInfo/getBusRouteList?ServiceKey=" + SKey.BusRouteInfo + "&strSrch=" + SBusNum);
                        //입력데이터 인코딩
                        SBusNum = Uri.EscapeUriString(SBusNum);
                        //비동기 호출, 호출 구분자로 GetBusRouteList를 사용함
                        wc.DownloadStringAsync(uri, "GetBusRouteList");

                        IsBusy = wc.IsBusy;
                    });
                }
                return getBusRouteListCommand;
            }
        }

        private ICommand selectionChangedCommand;
        /// <summary>
        /// 노선 목록에서 선택된 아이템이 변경된 경우 실행되는 커맨드
        /// </summary>
        public ICommand SelectionChangedCommand
        {
            get
            {
                if (selectionChangedCommand == null)
                {
                    selectionChangedCommand = new ActionCommand(item =>
                    {
                        //커맨드 파라메터로 전달 받은 오브젝트를 형변환
                        BusRouteModel route = item as BusRouteModel;
                        //형변환을 성공적으로 처리했다면
                        if (route != null)
                        {
                            //정류소 조회
                            var uri = new Uri("http://ws.bus.go.kr/api/rest/busRouteInfo/getStaionByRoute?ServiceKey=" + SKey.BusRouteInfo + "&busRouteId=" + route.BusRouteId);
                            wc.DownloadStringAsync(uri, "GetStaionByRoute");

                            //던지고 바로 정류소 목록 조회 화면으로 네비게이션
                            root.Navigate(new Uri("/Views/StationByRouteView.xaml", UriKind.Relative));
                        }
                    });
                }
                return selectionChangedCommand;
            }
        }

        private ICommand loadCommand;
        /// <summary>
        /// MainPage.xaml이 로드가 될때 실행
        /// </summary>
        public ICommand LoadCommand
        {
            get
            {
                if (loadCommand == null)
                {
                    loadCommand = new ActionCommand(() =>
                    {
                        //메인 프레임 입력 - 네비게이션하는데 필요
                        if (root == null)
                        {
                            //생성자에서는 이 데이터를 가지고 올 수가 없음..
                            root = App.Current.RootVisual as PhoneApplicationFrame;
                        }
                        //격리 저장소에 뷰모델을 저장
                        StaticFunctions.SaveToIS("MainPageViewModel", this);
                    });
                }
                return loadCommand;
            }
        }
    }
}

5. 버스 정보를 제공하는..

서버가 응답 속도가 평소에 비해 무지하게 느린 것이 날씨가 추워서 성능 저하가 된 것 같다.
앞으로 2회 강좌로 마무리를 할 수 있도록 노력하겠다.

 

 

 

 

 

'Windows Phone 8 > Korea Bus Information' 카테고리의 다른 글

Seoul Bus Info Search App Dev 9  (0) 2012.01.12
My BusInfo app deploy  (0) 2012.01.08
Seoul Bus Info Search App Dev 7  (0) 2012.01.07
Seoul Bus Info Search App Dev 6  (0) 2012.01.07
Seoul Bus Info Search App Dev 5  (0) 2012.01.07
Seoul Bus Info Search App Dev 4  (0) 2012.01.07
Posted by MVP kaki104

2012년 윈도우 폰이 잘 되어서.. 개발자들이 기를 펴고 살 수 있기를 기원하며..오늘의 강좌를 시작해 보자.
6번째 강좌에서는 선택된 버스의 정류장 정보를 조회해서 2번째 페이지에 출력하는 부분까지 진행을 하도록 하겠다.

1. 노선별 경유 정류소 목록 조회
(http://api.bus.go.kr/contents/sub02/getStaionByRoute.html)
(페이지의 내용 + 실제 데이터를 확인 한 후 모델을 작성하면 된다.)

StationByRouteModel.cs

using System.ComponentModel;

namespace BusInfo.Models
{
    //http://api.bus.go.kr/contents/sub02/getStaionByRoute.html
    //노선별 경유 정류소 목록 조회
    public class StationByRouteModel : INotifyPropertyChanged
    {
        #region PropertyChange
        public event PropertyChangedEventHandler PropertyChanged;

        private void FirePropertyChange(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        private string beginTm;
        /// <summary>
        /// 첫차시간
        /// </summary>
        public string BeginTm
        {
            get { return beginTm; }
            set
            {
                beginTm = value;
                FirePropertyChange("BeginTm");
            }
        }

        string lastTm;
        /// <summary>
        /// 막차시간
        /// </summary>
        public string LastTm
        {
            get { return lastTm; }
            set
            {
                lastTm = value;
                FirePropertyChange("LastTm");
            }
        }

        int busRouteId;
        /// <summary>
        /// 노선ID
        /// </summary>
        public int BusRouteId
        {
            get { return busRouteId; }
            set
            {
                busRouteId = value;
                FirePropertyChange("BusRouteId");
            }
        }

        string busRouteNm;
        /// <summary>
        /// 노선명
        /// </summary>
        public string BusRouteNm
        {
            get { return busRouteNm; }
            set
            {
                busRouteNm = value;
                FirePropertyChange("BusRouteNm");
            }
        }

        double gpsX;
        /// <summary>
        /// X좌표 WGS 84
        /// </summary>
        public double GpsX
        {
            get { return gpsX; }
            set
            {
                gpsX = value;
                FirePropertyChange("GpsX");
            }
        }

        double gpsY;
        /// <summary>
        /// Y좌표 WGS 84
        /// </summary>
        public double GpsY
        {
            get { return gpsY; }
            set
            {
                gpsY = value;
                FirePropertyChange("GpsY");
            }
        }

        int routeType;
        /// <summary>
        /// 노선유형
        /// </summary>
        public int RouteType
        {
            get { return routeType; }
            set
            {
                routeType = value;
                FirePropertyChange("RouteType");
            }
        }

        int section;
        /// <summary>
        /// 구간
        /// </summary>
        public int Section
        {
            get { return section; }
            set
            {
                section = value;
                FirePropertyChange("Section");
            }
        }

        int seq;
        /// <summary>
        /// 순번
        /// </summary>
        public int Seq
        {
            get { return seq; }
            set
            {
                seq = value;
                FirePropertyChange("Seq");
            }
        }

        int station;
        /// <summary>
        /// 정류소ID
        /// </summary>
        public int Station
        {
            get { return station; }
            set
            {
                station = value;
                FirePropertyChange("Station");
            }
        }

        string stationNm;
        /// <summary>
        /// 정류소명
        /// </summary>
        public string StationNm
        {
            get { return stationNm; }
            set
            {
                stationNm = value;
                FirePropertyChange("StationNm");
            }
        }

        int stationNo;
        /// <summary>
        /// 정류소 고유번호
        /// </summary>
        public int StationNo
        {
            get { return stationNo; }
            set
            {
                stationNo = value;
                FirePropertyChange("StationNo");
            }
        }

        float fullSectDist;
        /// <summary>
        /// 정류소간 거리
        /// </summary>
        public float FullSectDist
        {
            get { return fullSectDist; }
            set
            {
                fullSectDist = value;
                FirePropertyChange("FullSectDist");
            }
        }

        int trnstnId;
        /// <summary>
        /// 회차지정류소ID
        /// </summary>
        public int TrnstnId
        {
            get { return trnstnId; }
            set
            {
                trnstnId = value;
                FirePropertyChange("TrnstnId");
            }
        }
    }
}

F6키를 눌러서 빌드를 한번 해주고 MainPageViewModel.cs로 이동한다.

2. MainPageViewModel.cs 수정
정류소 목록을 저장할 컬렉션 프로퍼티를 하나 추가하고

ObservableCollection<StationByRouteModel> stationCollection;
/// <summary>
/// 노선별 경유 정류소 컬렉션 - 프로퍼티
/// </summary>
public ObservableCollection<StationByRouteModel> StationCollection
{
    get { return stationCollection; }
    set
    {
        stationCollection = value;
        FirePropertyChange("StationCollection");
    }
}

SelectionChangedCommand를 수정한다.
(네비게이션 서비스는 정류소 목록이 완성이 된 후에 넘어가야 하기 때문에 현재 커맨드 내부에 있는 네비게이션은 일단 삭제해 준다)
그리고, 정류소 조회 OpenAPI를 호출한다.

private ICommand selectionChangedCommand;
/// <summary>
/// 노선 목록에서 선택된 아이템이 변경된 경우 실행되는 커맨드
/// </summary>
public ICommand SelectionChangedCommand
{
    get
    {
        if (selectionChangedCommand == null)
        {
            selectionChangedCommand = new ActionCommand(item =>
            {
                //메인 프레임 입력 - 네비게이션하는데 필요
                if (root == null)
                {
                    //생성자에서는 이 데이터를 가지고 올 수가 없음..
                    root = App.Current.RootVisual as PhoneApplicationFrame;
                }

                //커맨드 파라메터로 전달 받은 오브젝트를 형변환
                BusRouteModel route = item as BusRouteModel;
                //형변환을 성공적으로 처리했다면
                if (route != null)
                {
                    //정류소 조회
                    var uri = new Uri("http://ws.bus.go.kr/api/rest/busRouteInfo/getStaionByRoute?ServiceKey=" + SKey.BusRouteInfo + "&busRouteId=" + route.BusRouteId);
                    wc.DownloadStringAsync(uri, "GetStaionByRoute");
                }

            });
        }
        return selectionChangedCommand;
    }
}

3. 블랜드로 전환 디자인 수정 - 커맨드 파라메터 추가
ListBox에 있는 InvokeCommandAction에 CommandParameter를 추가한다.  InvokeCommandAction에는 1개의 CommandParameter 입력이 가능하고, 선택된 파라메터 데이터는 뷰모델에 SelectionChangedCommand로 넘어 item에 담기게 된다.

InvokeCommandAction선택 -> 오른쪽 상단 프로퍼티에서 CommandParameter 오른쪽에 네모 클릭 -> Data Binding 선택 -> Element Property 텝 선택 -> Scene elements에서 ListBox 선택 -> Properties에서 SelectedItem 선택 -> OK


이렇게 입력을 한 후에 xaml을 확인 해보자

<ListBox x:Name="listBox" ItemTemplate="{StaticResource BusRouteDataTemplate}" ItemsSource="{Binding BusRouteCollection}">
 <i:Interaction.Triggers>
  <i:EventTrigger EventName="SelectionChanged">
   <i:InvokeCommandAction Command="{Binding SelectionChangedCommand, Mode=OneWay}" CommandParameter="{Binding SelectedItem, ElementName=listBox}"/>
  </i:EventTrigger>
 </i:Interaction.Triggers>
</ListBox>

CommandParameter로 listBox에 SelectedItem을 넘기도로 수정이 되어 있는 것을 볼 수 있다.
왜 SelectedItem을 파라메터로 넘기는 것일까? 우리는 MVVM 패턴을 사용하고, Object 프로그램을 하고 있으니 충분히 알 수 있을 것이라 생각한다.

4. VS2010으로 돌아와서 오퍼레이션 확인
wc.DownloadStringAsync(uri, "GetStaionByRoute"); //브레이크를 걸고 디버그 모드로 실행을 해보자
route에 데이터를 확인을 해보면 우리가 선택한 노선의 데이터가 들어가 있는 것을 확인 할 수 있다.

5. 정류소 조회 데이터 파싱
wc_DownloadStringCompleted 이벤트 처리 부를 수정한다.

case "GetStaionByRoute":
    //정류소 목록 파싱
    var stations = from item in xml.Descendants("itemList")
                   select new StationByRouteModel
                   {
                       BusRouteId = Convert.ToInt32(item.Element("busRouteId").Value),
                       BusRouteNm = item.Element("busRouteNm").Value,
                       Seq = Convert.ToInt32(item.Element("seq").Value),
                       Section = Convert.ToInt32(item.Element("section").Value),
                       Station = Convert.ToInt32(item.Element("station").Value),
                       StationNm = item.Element("stationNm").Value,
                       GpsX = Convert.ToDouble(item.Element("gpsX").Value),
                       GpsY = Convert.ToDouble(item.Element("gpsY").Value),
                       FullSectDist = Convert.ToSingle(item.Element("fullSectDist").Value),
                       StationNo = Convert.ToInt32(item.Element("stationNo").Value),
                       RouteType = Convert.ToInt32(item.Element("routeType").Value),
                       BeginTm = item.Element("beginTm").Value,
                       LastTm = item.Element("lastTm").Value,
                       TrnstnId = Convert.ToInt32(item.Element("trnstnid").Value)
                   };
    StationCollection = new ObservableCollection<StationByRouteModel>(stations);
    break;

디버그 모드로 실행해서 StationCollection에 데이터가 잘 들어와 지는지 확인 해보자. 정상적으로 데이터가 들어와 있으면 이제 페이지를 변경하고 그 페이지에 데이터를 보여주면 되는 것이다.

6. MVVM에서..
지금까지 사용해 본 경험에 의하면 View와 ViewModel은 1:1 관계를 이루는 것이 가장 심플하다. 하나의 뷰모델을 여러개의 뷰가 사용을 하게되면 복잡도가 올라가서 나중에 디버그 하기가 용이하지 않기 때문인데..그러나, 이 프로젝트에서는 하나의 뷰모델을 2개의 뷰에서 공유해서 사용하도록 하겠다.(여러가지 이유가..흐흐)

1개의 뷰모델을 공유해서 사용하는 방법은.. MEF가 가능한 Silverlight라면 문제가 되지 않치만 WP에서는 MEF를 지원하지 않기 때문에 약간 다른 방법(?)을 사용해서 처리 했는데... 그 방법은 격리저장소(IsolatedStorage)를 사용하는 것이다. 네비게이션 서비스는 String 파라메터를 넘길 수는 있지만 Object 파라메터를 넘길 수는 없기 때문이다.
(구글에서 찾아보면 Interface를 추가해서 사용하는 방법이 있기는 하지만..지금 쉽게 접근하지는 못할 것 같아서 다루지 않는다. 쉽게 Object를 넘길 수 있는 방법이 있으면 알려주기 바란다. 이 방법은 독자적으로 생각나서 만들어 본 것으로 찾아 보지는 않았다...혹시 있을지도..쿨럭)

Functions폴더에 StaticFunctions.cs 파일을 추가하고 코딩을 해보자

using System.IO.IsolatedStorage;

namespace BusInfo.Functions
{
    public static class StaticFunctions
    {
        /// <summary>
        /// 내부저장소에 데이터 저장
        /// </summary>
        /// <param name="key">키</param>
        /// <param name="data">데이터</param>
        /// <returns></returns>
        public static bool SaveToIS(string key, object data)
        {
            if (IsolatedStorageSettings.ApplicationSettings.Contains(key) == false)
            {
                IsolatedStorageSettings.ApplicationSettings.Add(key, data);
            }
            else
            {
                IsolatedStorageSettings.ApplicationSettings[key] = data;
            }
            return true;
        }

        /// <summary>
        /// 내부저장소에서 데이터 조회
        /// </summary>
        /// <param name="key">키</param>
        /// <returns>데이터</returns>
        public static object LoadFromIS(string key)
        {
            object data;
            if (IsolatedStorageSettings.ApplicationSettings.Contains(key) == false)
            {
                data = null;
            }
            else
            {
                data = IsolatedStorageSettings.ApplicationSettings[key];
            }

            return data;
        }
    }
}

그리고, MainPageViewModel.cs를 다시 수정한다.

case "GetStaionByRoute":
    //정류소 목록 파싱
    var stations = from item in xml.Descendants("itemList")
                   select new StationByRouteModel
                   {
                       BusRouteId = Convert.ToInt32(item.Element("busRouteId").Value),
                       BusRouteNm = item.Element("busRouteNm").Value,
                       Seq = Convert.ToInt32(item.Element("seq").Value),
                       Section = Convert.ToInt32(item.Element("section").Value),
                       Station = Convert.ToInt32(item.Element("station").Value),
                       StationNm = item.Element("stationNm").Value,
                       GpsX = Convert.ToDouble(item.Element("gpsX").Value),
                       GpsY = Convert.ToDouble(item.Element("gpsY").Value),
                       FullSectDist = Convert.ToSingle(item.Element("fullSectDist").Value),
                       StationNo = Convert.ToInt32(item.Element("stationNo").Value),
                       RouteType = Convert.ToInt32(item.Element("routeType").Value),
                       BeginTm = item.Element("beginTm").Value,
                       LastTm = item.Element("lastTm").Value,
                       TrnstnId = Convert.ToInt32(item.Element("trnstnid").Value)
                   };
    StationCollection = new ObservableCollection<StationByRouteModel>(stations);

    //정류소 목록 조회 화면으로 네비게이션
    root.Navigate(new Uri("/Views/StationByRouteView.xaml", UriKind.Relative));
    break;

MainPageViewModel 생성자에 추가

    //격리 저장소에 뷰모델을 저장
    StaticFunctions.SaveToIS("MainPageViewModel", this);


7. 블랜드에서 정류소 목록 페이지를 디자인 해보자


(혼자서 열심히 그려보도록 하자 : 하단은 ListBox이고 Border로 감싼 후 주위를 흰색으로 처리 했다.)
이제 DataContext를 붙여야 한다. 붙이는 방법은 MainPage.xaml에서 사용했던 방법과 동일하게 처리하면 된다.(모른다고 하면 때림^^;;;)
그리고, ListBox를 선택하고 -> ItemsSource 프로퍼티 오른쪽에 네모를 클릭해서 -> Data Binding -> StationCollection -> OK
지금까지의 StationByRouteView.xaml의 소스는 아래와 같다.

<phone:PhoneApplicationPage
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:BusInfo_ViewModels="clr-namespace:BusInfo.ViewModels"
    x:Class="BusInfo.Views.StationByRouteView"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
    shell:SystemTray.IsVisible="True">
 
    <!--Sample code showing usage of ApplicationBar-->
    <!--<phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1"/>
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
                <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>-->

 <d:DataContext>
  <BusInfo_ViewModels:MainPageViewModel/>
 </d:DataContext>

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="kaki104 App" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="경유정류소목록" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}" FontSize="64"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
         <Border BorderThickness="1" BorderBrush="White">
          <ListBox ItemsSource="{Binding StationCollection}"/>
         </Border>
        </Grid>
    </Grid>

</phone:PhoneApplicationPage>

여기까지만 하면 디자인 타임에 MainPageViewModel.cs에 있는 프로퍼티를 사용하는 것은 문제가 없다. 하지만, 런타임에는 아무것도 나오지 않는다. StationByRouteView.xaml.cs에 추가로 코딩을 하자.

8. StationByRouteView.xaml.cs

using BusInfo.Functions;
using BusInfo.ViewModels;
using Microsoft.Phone.Controls;

namespace BusInfo.Views
{
    public partial class StationByRouteView : PhoneApplicationPage
    {
        //뷰모델을 프로퍼티로 선언하고
        public MainPageViewModel ViewModel { get; set; }

        /// <summary>
        /// 생성자
        /// </summary>
        public StationByRouteView()
        {
            InitializeComponent();

            //격리 저장소에 있는 뷰모델을 불러와서 뷰모델에 넣고
            ViewModel = StaticFunctions.LoadFromIS("MainPageViewModel") as MainPageViewModel;
            //뷰모델을 데이터컨텍스트에 입력
            this.DataContext = ViewModel;
        }
    }
}

여기까지 코딩 후 실행 한다.


선택한 버스의 정류소 목록들이 표시(?)가 되었다. 위와 같이 나오는 것은 바인딩 되어있는 모델의 이름이 보이고 있는 것이다.
일단은 정류소 이름이라도 보이도록 처리해보자.

블랜드에서 ListBox를 선택 후 DisplayMemberPath 프로퍼티에 StationNm이라고 입력한다.(이건 바인딩이 아니다 직접 입력한다). 그리고 다시 실행 해 본다. 3100안산 버스를 클릭 후
 


<- 버튼을 눌러서 뒤로 돌아간 후 3100의정부 버스를 클릭 했다.
 

정류소 목록이 변경되는 것을 알 수 있다.

여기서 의문을 가져야 하는 부분이 한가지 있다. 과연 MainPageViewModel은 어떻게 된 것일까?
MainPage.xaml의 생성자에서 한번 저장해주고, StationByRouteView.xaml의 생성자에서 한번 불러왔을 뿐인데..어떻게..
그건 여러분의 몫으로 남겨 두겠다.

MainPageViewModel.cs 전체 소스

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Input;
using System.Xml.Linq;
using BusInfo.Functions;
using BusInfo.Models;
using Microsoft.Expression.Interactivity.Core;
using Microsoft.Phone.Controls;

namespace BusInfo.ViewModels
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        #region PropertyChange
        public event PropertyChangedEventHandler PropertyChanged;

        private void FirePropertyChange(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        /// <summary>
        /// 검색할 버스 노선 번호
        /// </summary>
        public string SBusNum { get; set; }

        /// <summary>
        /// 웹 클라이언트
        /// </summary>
        WebClient wc;

        /// <summary>
        /// 인증 키
        /// </summary>
        ServiceKey SKey;

        ObservableCollection<BusRouteModel> busRouteCollection;
        /// <summary>
        /// 버스 목록 컬렉션 - 프로퍼티
        /// </summary>
        public ObservableCollection<BusRouteModel> BusRouteCollection
        {
            get { return busRouteCollection; }
            set
            {
                busRouteCollection = value;
                FirePropertyChange("BusRouteCollection");
            }
        }

        ObservableCollection<StationByRouteModel> stationCollection;
        /// <summary>
        /// 노선별 경유 정류소 컬렉션 - 프로퍼티
        /// </summary>
        public ObservableCollection<StationByRouteModel> StationCollection
        {
            get { return stationCollection; }
            set
            {
                stationCollection = value;
                FirePropertyChange("StationCollection");
            }
        }

        //메인 프레임
        PhoneApplicationFrame root;

        /// <summary>
        /// 생성자
        /// </summary>
        public MainPageViewModel()
        {
            //웹클라이언트 생성
            wc = new WebClient();
            wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
            //서비스키 생성
            SKey = new ServiceKey();

            //격리 저장소에 뷰모델을 저장
            StaticFunctions.SaveToIS("MainPageViewModel", this);
        }

        /// <summary>
        /// 웹클라이언트 다운로드스트링 컴플릿트 이벤트 구현
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Error != null)
                return;
            //xml을 xElement라는 객체로 바로 파싱해서 사용한다.
            XElement xml = XElement.Parse(e.Result);
            //어떤 서비스의 결과인지에 따라서 각각 다른 처리를 한다.
            switch (e.UserState as string)
            {
                case "GetBusRouteList":
                    //버스 노선 목록 파싱
                    var rows = from item in xml.Descendants("itemList")
                               select new BusRouteModel
                               {
                                   BusRouteId = Convert.ToInt32(item.Element("busRouteId").Value),
                                   BusRouteNm = item.Element("busRouteNm").Value,
                                   StartStationNm = item.Element("stStationNm").Value,
                                   EndStationNm = item.Element("edStationNm").Value,
                                   RouteType = Convert.ToInt32(item.Element("routeType").Value),
                                   Term = Convert.ToInt32(item.Element("term").Value)
                               };
                    BusRouteCollection = new ObservableCollection<BusRouteModel>(rows);

                    break;
                case "GetStaionByRoute":
                    //정류소 목록 파싱
                    var stations = from item in xml.Descendants("itemList")
                                   select new StationByRouteModel
                                   {
                                       BusRouteId = Convert.ToInt32(item.Element("busRouteId").Value),
                                       BusRouteNm = item.Element("busRouteNm").Value,
                                       Seq = Convert.ToInt32(item.Element("seq").Value),
                                       Section = Convert.ToInt32(item.Element("section").Value),
                                       Station = Convert.ToInt32(item.Element("station").Value),
                                       StationNm = item.Element("stationNm").Value,
                                       GpsX = Convert.ToDouble(item.Element("gpsX").Value),
                                       GpsY = Convert.ToDouble(item.Element("gpsY").Value),
                                       FullSectDist = Convert.ToSingle(item.Element("fullSectDist").Value),
                                       StationNo = Convert.ToInt32(item.Element("stationNo").Value),
                                       RouteType = Convert.ToInt32(item.Element("routeType").Value),
                                       BeginTm = item.Element("beginTm").Value,
                                       LastTm = item.Element("lastTm").Value,
                                       TrnstnId = Convert.ToInt32(item.Element("trnstnid").Value)
                                   };
                    StationCollection = new ObservableCollection<StationByRouteModel>(stations);

                    //정류소 목록 조회 화면으로 네비게이션
                    root.Navigate(new Uri("/Views/StationByRouteView.xaml", UriKind.Relative));
                    break;
            }

            if (1 == 1)
            {
            }
        }

        private ICommand getBusRouteListCommand;
        /// <summary>
        /// 노선 목록 조회 커맨드
        /// </summary>
        public ICommand GetBusRouteListCommand
        {
            get
            {
                if (getBusRouteListCommand == null)
                {
                    getBusRouteListCommand = new ActionCommand(() =>
                    {
                        if (SBusNum == null || SBusNum.Length <= 1)
                        {
                            MessageBox.Show("2글자 이상 입력해야 합니다.");
                            return;
                        }
                        //REST 형식 OpenAPI 호출
                        var uri = new Uri("http://ws.bus.go.kr/api/rest/busRouteInfo/getBusRouteList?ServiceKey=" + SKey.BusRouteInfo + "&strSrch=" + SBusNum);
                        //입력데이터 인코딩
                        SBusNum = Uri.EscapeUriString(SBusNum);
                        //비동기 호출, 호출 구분자로 GetBusRouteList를 사용함
                        wc.DownloadStringAsync(uri, "GetBusRouteList");
                    });
                }
                return getBusRouteListCommand;
            }
        }

        private ICommand selectionChangedCommand;
        /// <summary>
        /// 노선 목록에서 선택된 아이템이 변경된 경우 실행되는 커맨드
        /// </summary>
        public ICommand SelectionChangedCommand
        {
            get
            {
                if (selectionChangedCommand == null)
                {
                    selectionChangedCommand = new ActionCommand(item =>
                    {
                        //메인 프레임 입력 - 네비게이션하는데 필요
                        if (root == null)
                        {
                            //생성자에서는 이 데이터를 가지고 올 수가 없음..
                            root = App.Current.RootVisual as PhoneApplicationFrame;
                        }

                        //커맨드 파라메터로 전달 받은 오브젝트를 형변환
                        BusRouteModel route = item as BusRouteModel;
                        //형변환을 성공적으로 처리했다면
                        if (route != null)
                        {
                            //정류소 조회
                            var uri = new Uri("http://ws.bus.go.kr/api/rest/busRouteInfo/getStaionByRoute?ServiceKey=" + SKey.BusRouteInfo + "&busRouteId=" + route.BusRouteId);
                            wc.DownloadStringAsync(uri, "GetStaionByRoute");
                        }
                    });
                }
                return selectionChangedCommand;
            }
        }
    }
}

8. 이번 강좌는 여기서 마무리 하겠다.
윈도우 폰을 오늘 하얀색으로 예약을 했는데..언제 받을 수 있을지.. 받으면 이것 저것 할 일은 많은데..ㅎㅎ 그런데, 기분이 나쁜 점은 윈도우 폰을 팔기 시작 했으면서 어떻게 광고 한번 하는 걸 꼴을 못 봤다는 것이다. I랑 S팔때는 하루 종일 광고 하더니..윈도폰도 그정도 광고 했으면 잘 나갔을 것인데..쩝..
다시 한번 2012년은 윈도우폰이 뜨는 원년이 되었으면 한다.

'Windows Phone 8 > Korea Bus Information' 카테고리의 다른 글

My BusInfo app deploy  (0) 2012.01.08
Seoul Bus Info Search App Dev 7  (0) 2012.01.07
Seoul Bus Info Search App Dev 6  (0) 2012.01.07
Seoul Bus Info Search App Dev 5  (0) 2012.01.07
Seoul Bus Info Search App Dev 4  (0) 2012.01.07
Seoul Bus Info Search App Dev 3  (0) 2012.01.04
Posted by MVP kaki104

눈인지 비인지 알 수 없는 넘(?)들이 하늘에서 내려오는 날이다. 이렇게 좋은 날 행복한 망고 강좌 5번의 시작을 장식해서 살짝 흥분된다. 흐흐;;; 이번 강좌에서는 컨버터를 만들어서 사용하는 방법과 오류 처리와 네비게이션에 대해 간단하게 정리하고 넘어가도록 하겠다.

1. Converter만들기
우리가 필요한 건 routeType을 가지고 택스트 데이터를 반환하는 것과 routeType과 동일한 컬러를 반환하는 2가지의 컨버터를 만들 것이다.

VS2010에서 작업

Converters 폴더 선택 -> Add -> Class -> RouteTypeToColorConverter.cs
Converters 폴더 선택 -> Add -> Class -> RouteTypeToNameConverter.cs
이렇게 2개를 추가해 놓고 먼저 RouteTypeToNameConverter.cs를 먼저 만들어보자

using System;
using System.Windows.Data;

namespace BusInfo.Converters
{
    public class RouteTypeToNameConverter : IValueConverter
    {
        //코드를 텍스트로
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            //컨버터로 넘어오는 데이터를 숫자형으로 변경
            int routeType = System.Convert.ToInt32(value);
            //반환될 데이터를 문자형으로 생성
            string returnValue = string.Empty;

            //노선 유형(1:공항, 3:간선, 4:지선, 5:순환, 6:광역, 7:인천, 8:경기, 9:폐지, 0:공용)
            switch (routeType)
            {
                case 0:
                    returnValue = "공용";
                    break;
                case 1:
                    returnValue = "공항";
                    break;
                case 3:
                    returnValue = "간선";
                    break;
                case 4:
                    returnValue = "지선";
                    break;
                case 5:
                    returnValue = "순환";
                    break;
                case 6:
                    returnValue = "광역";
                    break;
                case 7:
                    returnValue = "인천";
                    break;
                case 8:
                    returnValue = "경기";
                    break;
                case 9:
                    returnValue = "폐지";
                    break;
            }
            return returnValue;
        }

        //반대의 경우 처리하는 코드를 쓰는 부분인데..요기서는 필요 없다
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

다음은 컬러 RouteTypeToColorConverter.cs

using System;
using System.Windows.Data;
using System.Windows.Media;

namespace BusInfo.Converters
{
    public class RouteTypeToColorConverter : IValueConverter
    {
        //코드를 브러쉬로
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            //컨버터로 넘어오는 데이터를 숫자형으로 변경
            int routeType = System.Convert.ToInt32(value);
            //반환될 데이터를 브러쉬로 생성
            Brush returnValue;

            //노선 유형(1:공항, 3:간선, 4:지선, 5:순환, 6:광역, 7:인천, 8:경기, 9:폐지, 0:공용)
            switch (routeType)
            {
                case 1:
                    returnValue = new SolidColorBrush(Colors.Orange);
                    break;
                case 3:
                    returnValue = new SolidColorBrush(Colors.Blue);
                    break;
                case 4:
                    returnValue = new SolidColorBrush(Colors.Green);
                    break;
                case 7:
                    returnValue = new SolidColorBrush(Colors.Magenta);
                    break;
                case 8:
                    returnValue = new SolidColorBrush(Colors.Red);
                    break;
                default:
                    returnValue = new SolidColorBrush(Colors.White);
                    break;
            }

            return returnValue;
        }

        //반대의 경우 처리하는 코드를 쓰는 부분인데..요기서는 필요 없다
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

F6 눌러서 빌드 후에 블랜드로 이동

상단에  [ ListBox ] -> Edit Additional Templetes -> Edit Generated Items (ItemTemplate) -> Edit Current 클릭


RouteType이 바인딩 되어있는 TextBlock를 선택 -> Text 프로퍼티 오른쪽에 노란색 네모 클릭 -> Data Binding 클릭

요기까지 나오게 한 다음에
Value converter 오른쪽에 [...] 버튼을 클릭 ->

RouteTypeToNameConverter 선택 -> OK -> OK
컨버터 적용하는 것도 매우 간단하다는 것을 알 수 있다.

이번에는 Border에 유형별 색상을 적용시켜 보자. (BorderThickness는 적당한 값으로 설정한다.)
Border를 선택 ->오른쪽 Brushes -> BorderBrush 프로퍼티 오른쪽에 네모 클릭 -> Data Binding 선택 ->


Show -> All Properties -> RouteType 선택 -> 역삼각형 모양 클릭해서 아래 넓히고 ->
Value converter 오른쪽에 […] 클릭 -> RouteTypeToColorConverter선택 - Ok

위의 상태까지 동일하면 Ok눌러서 바인딩 완료

데이터 템플릿의 최종 소스

<ResourceDictionary
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:BusInfo_Converters="clr-namespace:BusInfo.Converters">

 <BusInfo_Converters:RouteTypeToColorConverter x:Key="RouteTypeToColorConverter"/>
 <!-- Resource dictionary entries should be defined here. -->
 <BusInfo_Converters:RouteTypeToNameConverter x:Key="RouteTypeToNameConverter"/>
 
 <DataTemplate x:Key="BusRouteDataTemplate">
  <Border BorderThickness="10,1,0,0" Width="450" BorderBrush="{Binding RouteType, Converter={StaticResource RouteTypeToColorConverter}}" >
   <Grid>
    <Grid.RowDefinitions>
     <RowDefinition Height="0.5*"/>
     <RowDefinition Height="0.5*"/>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal">
     <TextBlock TextWrapping="Wrap" Text="◎ 종류 : "/>
     <TextBlock TextWrapping="Wrap" Text="{Binding RouteType, Converter={StaticResource RouteTypeToNameConverter}}"/>
     <TextBlock TextWrapping="Wrap" Text="◎ 버스명 : " Margin="6,0,0,0"/>
     <TextBlock TextWrapping="Wrap" Text="{Binding BusRouteNm}" />
    </StackPanel>
    <StackPanel Grid.Row="1" Orientation="Horizontal">
     <TextBlock TextWrapping="Wrap" Text="◎ 노선 : "/>
     <TextBlock Text="{Binding StartStationNm}"/>
     <TextBlock TextWrapping="Wrap" Text=" ~ "/>
     <TextBlock Text="{Binding EndStationNm}"/>
    </StackPanel>
   </Grid>
  </Border>
 </DataTemplate>
</ResourceDictionary>

이렇게만 만들고 실행하면 ListBox에 구분선이 없어서 좀 허전한 느낌이 든다.
그래서 ListBox도 Border로 감싸고 색을 좀 주고, 노선번호 입력하는 텍스트박스는 숫자만 입력이 가능하면 되기 때문에 그 부분도 약간 손을 본다.
 MainPage.xaml의 최종 소스는 아래와 같다.

<phone:PhoneApplicationPage
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:BusInfo_ViewModels="clr-namespace:BusInfo.ViewModels"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    x:Class="BusInfo.MainPage"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">
 
    <!--Sample code showing usage of ApplicationBar-->
    <!--<phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1"/>
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
                <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>-->

 <d:DataContext>
  <BusInfo_ViewModels:MainPageViewModel/>
 </d:DataContext>

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="kaki104 App" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="버스 목록 조회" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}" FontSize="64" />
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
         <Grid.RowDefinitions>
          <RowDefinition Height="72"/>
          <RowDefinition/>
         </Grid.RowDefinitions>
         <StackPanel Orientation="Horizontal">
          <TextBlock TextWrapping="Wrap" Text="노선번호" VerticalAlignment="Center"/>
          <TextBox TextWrapping="Wrap" Text="{Binding SBusNum, Mode=TwoWay}" Width="275" InputScope="Number"/>
          <Button Content="조회">
           <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
             <i:InvokeCommandAction Command="{Binding GetBusRouteListCommand, Mode=OneWay}"/>
            </i:EventTrigger>
           </i:Interaction.Triggers>
          </Button>
         </StackPanel>
         <Border Grid.Row="1" BorderThickness="1" BorderBrush="White" >
          <ListBox ItemTemplate="{StaticResource BusRouteDataTemplate}" ItemsSource="{Binding BusRouteCollection}"/>
         </Border>
        </Grid>
    </Grid>

</phone:PhoneApplicationPage>

작업이 완료된 후에 실행해서 결과를 확인 하자


컨버터를 이용해서 노선 유형을 택스트로 변경하고, 색까지 표현하도록 만들어 보았다.

2. 오류 처리를 하나 추가해 보자

앱을 실행하고 바로 번호를 입력하지 않은 상태에서 조회를 눌러보자

GetBusRouteListCommand에 있는
if (SBusNum.Length <= 1)
{
    MessageBox.Show("2글자 이상 입력해야 합니다.");
    return;
}
이 부분에서 오류가 발생한다. SbusNum이 Null값을 가지기 때문에 Length를 알 수 없기 때문이니..
간단하게 수정을 하자

if (SBusNum == null || SBusNum.Length <= 1)
{
    MessageBox.Show("2글자 이상 입력해야 합니다.");
    return;
}

3. 네비게이션 기능 추가
네비게이션이 무엇인지는 다들 알고 있을 것이라고 생각하고, 설명은 하지 않겠다. 여기서는 윈도우 폰의 페이지에서 어떻게 네비게이션 처리를 하는지만 알아 보도록 하자.
기본적인 네비게이션 서비스는 View에서만 적용이 가능한데, 이 것을 뷰모델에서 가능하도록 코드를 추가해 주어야한다. 우선, MainPageViewModel.cs 뷰모델에 추가 작업을 한다.

//메인 프레임
PhoneApplicationFrame root;
그리고, 커맨드를 하나 추가한다. 커맨드 이름은 SelectionChangedCommand이다.

private ICommand selectionChangedCommand;
/// <summary>
/// 노선 목록에서 선택된 아이템이 변경된 경우 실행되는 커맨드
/// </summary>
public ICommand SelectionChangedCommand
{
    get
    {
        if (selectionChangedCommand == null)
        {
            selectionChangedCommand = new ActionCommand(() =>
            {
                //메인 프레임 입력 - 네비게이션하는데 필요
                if (root == null)
                {
                    //생성자에서는 이 데이터를 가지고 올 수가 없음..
                    root = App.Current.RootVisual as PhoneApplicationFrame;
                }
                //Uri에 있는 xaml파일은 조금 있다 추가한다.
                root.Navigate(new Uri("/Views/StationByRouteView.xaml", UriKind.Relative));
            });
        }
        return selectionChangedCommand;
    }
}

이제 프로젝트에 Views폴더 선택 -> Add -> New Item -> Windows Phone Portrait Page -> Name : StationByRouteView.xaml -> OK
F6을 눌러서 빌드를 한번 하고, 블랜드로 넘어가서

MainPage.xaml에 있는 ListBox에 ListBox에 InvokeCommandAction을 추가한다. (추가하는 방법은 3회 강좌를 참고한다.)
추가한 InvokeCommandAction를 선택하고 오른쪽 상단 EventName을 SelectionChanged 이벤트를 선택하고 Command 프로퍼티 오른쪽에 하드 디스크 모양 아이콘을 눌러서 Create Data Binding에서 SelectionChangedCommand를 선택하고 OK를 누른다. (저장도 꼭)


저장 까지 완료 한 상태의 화면

4. 최종 테스트


105번 선택

네비게이션 서비스 까지 완료된 것을 확인 할 수 있다.

6. 여기까지 했으니 이제 절반은 완성 한 것 같다.
만드는 방법을 자세하게 한다고 했는데.. 잘 되었는지 모르겠다. 질문은 리플로 남겨 주기 바란다

'Windows Phone 8 > Korea Bus Information' 카테고리의 다른 글

Seoul Bus Info Search App Dev 7  (0) 2012.01.07
Seoul Bus Info Search App Dev 6  (0) 2012.01.07
Seoul Bus Info Search App Dev 5  (0) 2012.01.07
Seoul Bus Info Search App Dev 4  (0) 2012.01.07
Seoul Bus Info Search App Dev 3  (0) 2012.01.04
Seoul Bus Info Search App Dev 2  (0) 2012.01.04
Posted by MVP kaki104