티스토리 뷰
블로그 이전을 성공적으로 마무리 했으니, 이제 강좌를 마무리 할 차례인 것 같다. 그런데 블로그 유입 통계를 보면 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="경유정류소 & 위치" 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이 되어야지만 실행이 가능하다.)
내일 부터는 버스가 언제 올지 궁금해 하지 않아도 되겠다. 하하하
'Previous Platforms' 카테고리의 다른 글
Windows Phone Manager Tool (0) | 2012.01.21 |
---|---|
BusInfo for Windows8 Metro UI (0) | 2012.01.19 |
Tile Push Notifications Operation (0) | 2012.01.07 |
Tile create (0) | 2012.01.07 |
Basic sample (0) | 2012.01.07 |
- Total
- Today
- Yesterday
- MVVM
- #MVVM
- uno-platform
- UWP
- Bot Framework
- Cross-platform
- Microsoft
- .net
- XAML
- .net 5.0
- C#
- Windows 10
- kiosk
- dotNETconf
- visual studio 2019
- IOT
- Behavior
- ComboBox
- LINQ
- #uwp
- Always Encrypted
- Visual Studio 2022
- ef core
- uno platform
- PRISM
- WPF
- windows 11
- Build 2016
- #Windows Template Studio
- #prism
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |