WebBrowser 컨트롤과 App의 연동 작업

1. 참고 포스트
Silverlight WebBrowser Control and Windows Phone 7
http://blog.markarteaga.com/SilverlightWebBrowserControlAndWindowsPhone7.aspx


2. 자세한 사항은
위의 포스트를 참고하면 Step by Step으로 작업을 할 수 있다.
중요한 사항은 앱에서 특정 데이터를 WebBrowser 내부에 표시되는 페이지로 보내거나, 반대로 페이지의 데이터를 앱으로 받아 올 수 있다는 것이다.

3. MainPage.xaml.cs

using System;
using System.Windows;
using Microsoft.Phone.Controls;

namespace WebBrowserPhoneApp
{  
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructor
        public MainPage()
        {
            InitializeComponent();

            webBrowser1.ScriptNotify += new EventHandler<NotifyEventArgs>(webBrowser1_ScriptNotify);

            txtUrl.Text = "http://kaki105.cafe24.com/sample/webBrowserTest.htm";
        }

        void webBrowser1_ScriptNotify(object sender, NotifyEventArgs e)
        {
            MessageBox.Show(string.Format("New value received from web browser control (\"{0}\")", e.Value));
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            webBrowser1.InvokeScript("DataReceivedFromPhoneApp", "test data");
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            webBrowser1.Navigate(new Uri(txtUrl.Text, UriKind.Absolute));
        }
    }
}

4. 실행
1) 프로그램 실행 후 go 버튼을 눌러서 특정 페이지로 이동한다. (html 내부의 자바 스크립트는 참고 포스트에 나와 있다)

2) 일단 Send to browser 버튼을 눌러서 앱에서 페이지로 데이터를 전송 한다.


3) Enter your text를 web page data로 변경 후 Send to Phone 버튼을 눌러서 페이지의 데이터를 앱으로 전송한다.


5. 웹 페이지와 앱의 연동은
개발의 영역을 더 넓힐 수 있는 기회가 되는 것 같다.

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

daum, naver 등 대형 포탈의 경우 oAuth 인증을 이용한 접근을 이용하면, 개인의 가입된 카페 목록이나 해당 카페의 목록등을 CRUD할 수 있게 된다. 그런데.. naver의 경우 oAuth 인증을 할때 .net 기반은 사용하지 말라고 하는 의지가 좀 보인다. 그래서, 일단 daum의 oAuth를 먼저 작성을 하였다.

1. 참고 자료
OAuth 시작하기
http://dna.daum.net/apis/oauth

danielcrenna / oauth
https://github.com/danielcrenna/oauth

OAuth 초보자 가이드 - Part 3 : 보안 아키텍쳐
http://blog.naver.com/PostView.nhn?blogId=redspider0&logNo=70126973417


2. 핵심만 먼저 이야기를 해보자
oAuth 인증이면 무조건 Header에 인증 관련 데이터를 모두 넣어서 보내주면 된다. 무지 간단한 것인데..그 Header에 넣을 데이터를 구성하는 것이 좀 까다롭다. 그래서 danielcrenna / oauth 이 곳에 있는 dll을 포함해서 작업을 한다. vs2010에서는 NuGet으로 포함 시키면 된다.

Manage NuGet Package for Solution -> oauth로 검색하고, 뒤로 좀 넘어가면 OAuth라는 것이 보인다. 인스톨을 하자.



3. MainPage.xaml.cs

using System;
using System.Linq;
using System.Net;
using System.Text;
using System.Windows;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Tasks;
using OAuth;

//oAuth 관리 객체에 대한 오픈 소스 : https://github.com/danielcrenna/oauth
//NuGet package OAuth로 검색 OAuth, An implementation of OAuth 1.0a for .Net 설치
//핵심 : 각 단계마다 oAuth관리 객체에 필요한 데이터를 넣고 .GetAuthorizationHeader()를 호출해서
//       헤더 정보를 만든 후 request 객체의 해더에 방금 만든 정보를 추가하고 호출
namespace DaumPhoneApp
{
    public partial class MainPage : PhoneApplicationPage
    {
        //daum oAuth : http://dna.daum.net/apis/oauth
        //컨슈머 등록 : https://apis.daum.net/oauth/consumer/input
        //컨슈머 키, 시크릿 - 자신의 키를 입력해야 한다.
        const string consumerKey = "";
        const string consumerSecret = "";

        //url들
        string requestUrl = "https://apis.daum.net/oauth/requestToken";
        string userAuthorizeUrl = "https://apis.daum.net/oauth/authorize";
        string accessUrl = "https://apis.daum.net/oauth/accessToken";
        string callbackUrl = "oob";

        //oAuth관리 객체
        OAuthRequest client;

        // Constructor
        public MainPage()
        {
            InitializeComponent();

            this.Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            //oAuth 관리 객체 생성
            client = new OAuthRequest
            {
                Method = "GET",
                ConsumerKey = consumerKey,
                ConsumerSecret = consumerSecret,
                CallbackUrl = callbackUrl,
                SignatureMethod = OAuthSignatureMethod.HmacSha1,
                Realm = "https://apis.daum.net/oauth",
                Version = "1.0"
            };
        }

        //Request Token 받아오기
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //request token
            client.RequestUrl = requestUrl;
            var auth = client.GetAuthorizationHeader();

            var request = (HttpWebRequest)WebRequest.Create(client.RequestUrl);
            request.Headers["Authorization"] = auth;
            request.BeginGetResponse(new AsyncCallback(callBack), request);
        }

        private void callBack(IAsyncResult asyncResult)
        {
            //결과 처리
            var request = (HttpWebRequest)asyncResult.AsyncState;
            var response = request.EndGetResponse(asyncResult);
            var content = response.GetResponseStream();
            int length = Convert.ToInt32(content.Length);
            byte[] bytes = new byte[length];
            content.Read(bytes, 0, length);

            Encoding enc = Encoding.UTF8;
            string myString = enc.GetString(bytes, 0, length);
            //받아온 토큰을 client에 입력
            var splite = myString.Split('&');
            var token = from kkk in splite
                        let sp = kkk.Split('=')
                        select new { key = sp[0], value = sp[1] };
            client.Token = token.FirstOrDefault(p => p.key == "oauth_token").value;
            client.TokenSecret = token.FirstOrDefault(p => p.key == "oauth_token_secret").value;

            //사용자 인증 페이지 출력
            WebBrowserTask webTask = new WebBrowserTask();
            var uri = new Uri(userAuthorizeUrl + "?oauth_callback=oob&oauth_token=" + client.Token, UriKind.Absolute);
            webTask.Uri = uri;
            webTask.Show();
        }

        private void Button2_Click(object sender, RoutedEventArgs e)
        {
            //사용자 인증 번호 입력 후 accessToken 받기 위해서 다시 호출
            client.RequestUrl = accessUrl;
            client.Verifier = txtVerifier.Text;
            var auth = client.GetAuthorizationHeader();

            var request = (HttpWebRequest)WebRequest.Create(client.RequestUrl);
            request.Headers["Authorization"] = auth;
            request.BeginGetResponse(new AsyncCallback(callBack2), request);
        }

        private void callBack2(IAsyncResult asyncResult)
        {
            //결과 처리
            var request = (HttpWebRequest)asyncResult.AsyncState;
            var response = request.EndGetResponse(asyncResult);
            var content = response.GetResponseStream();
            int length = Convert.ToInt32(content.Length);
            byte[] bytes = new byte[length];
            content.Read(bytes, 0, length);

            Encoding enc = Encoding.UTF8;
            string myString = enc.GetString(bytes, 0, length);
            //accessToken 을 다시 client에 입력
            var splite = myString.Split('&');
            var token = from kkk in splite
                        let sp = kkk.Split('=')
                        select new { key = sp[0], value = sp[1] };
            client.Token = token.FirstOrDefault(p => p.key == "oauth_token").value;
            client.TokenSecret = token.FirstOrDefault(p => p.key == "oauth_token_secret").value;

            //버튼 활성화
            Dispatcher.BeginInvoke(() =>
            {
                btnGetCafeList.IsEnabled = true;
            });
       }

        private void Button3_Click(object sender, RoutedEventArgs e)
        {  
            //카페 목록 조회
            //client.RequestUrl = "http://apis.daum.net/cafe/alimis.xml";
            client.RequestUrl = "http://apis.daum.net/cafe/favorite_cafes.xml";
            var auth = client.GetAuthorizationHeader();
            var request = (HttpWebRequest)WebRequest.Create(client.RequestUrl);
            request.Headers["Authorization"] = auth;
            request.BeginGetResponse(new AsyncCallback(callBack3), request);
        }

        private void callBack3(IAsyncResult asyncResult)
        {
            //결과 처리
            var request = (HttpWebRequest)asyncResult.AsyncState;
            var response = request.EndGetResponse(asyncResult);
            var content = response.GetResponseStream();
            int length = Convert.ToInt32(content.Length);
            byte[] bytes = new byte[length];
            content.Read(bytes, 0, length);

            Encoding enc = Encoding.UTF8;
            string cafeList = enc.GetString(bytes, 0, length);

            //카페 목록 출력
            Dispatcher.BeginInvoke(() =>
            {
                tbList.Text = cafeList;
            });
        }
    }
}

4. 실행
1) 첫 화면 - Request Token 버튼을 클릭해서 토큰을 받고 사용자 인증 페이지로 넘어간다.


2) 사용자 인증 페이지로 이동


3) 로그인 먼저 하고..


4) 아이디 인증 - 확인


5) 인증 번호를 적어서 본 프로그램으로 이동 (pc에서 하면 복사 기능이 되는데.. 윈폰에서는 않됨)


6) 인증 번호를 입력 후 ok 버튼 클릭 -> Get Cafe List 버튼이 활성화 됨

7) 자주가는 카페 목록 조회 성공


5. 소스 정리를 하지 않았다.
이 포스트는 oAuth 인증하는 과정을 알려주기 위해서 있는 그대로의 자연스러움(?)을 유지 하려고 노력했다. 이 셈플 만들기 까지 약 10시간 정도의 시간이 걸린 것 같다. 앞으로는 oAuth가 나와도 겁내지 말고 잘 써보도록 하자.

 

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

이전에 완성 했던 Authentication in Silverlight 5 & SQL CE 4.0 포스트에서 사용되었던 인증 서비스를 이번에는 윈폰에서 사용 하도록 하겠다. 실버라이트에서는 DomainService를 OData Endpoint로 연결 했다면, WP7에서는 Soap Endpoint로 연결한다는 점만 유의 하면 된다.

이전 포스트
Authentication in Silverlight 5 & SQL CE 4.0
http://kaki104.tistory.com/67
* 이 포스트의 자료를 다운 받아서 WP7Host.Web 프로젝트를 실행을 시켜 놓은 상태에서 아래에 있는 내용을 따라 한다.


1. Service References에 인증용 서비스를 추가한다.
추가할 주소는 http://localhost:33113/Services/WP7Host-Web-AuthenticationService.svc , 이름은 AuthenticationServiceReference 로 한다.

1) 일단, 서비스 레퍼런스를 추가하면 한가지 문제가 발생한다.
기존에 조회를 하고 있던 WP7ServiceReference의 서비스와 새로 추가한 AuthenticationServiceReference가 서로 충돌이 발생하는데. 기존에 만들어 놓았던 내용들에 NMessenger.WP7ServiceReference. 를 추가해서 구분을 시켜 준다.

2) 로그인 후의 정보는 쿠키를 이용해야 하기 때문에 쿠키를 사용 가능하도록 변경한다.
ServiceReferences.ClientConfig 파일을 열어서

  <basicHttpBinding>
      <binding name="BasicHttpBinding_WP7DomainServiceSoap" maxBufferSize="2147483647"
          enableHttpCookieContainer="true"
          maxReceivedMessageSize="2147483647">
          <security mode="None" />
      </binding>
      <binding name="BasicHttpBinding_AuthenticationServiceSoap" maxBufferSize="2147483647"
          enableHttpCookieContainer="true"
          maxReceivedMessageSize="2147483647">
          <security mode="None" />
      </binding>
  </basicHttpBinding>


2. MainPageViewModel.cs 추가 코드

  //Authentication Soap Client
  AuthenticationServiceSoapClient authClient;

...

  private string userID;
  /// <summary>
  /// 사용자 아이디
  /// </summary>
  public string UserID
  {
      get { return userID; }
      set
      {
          userID = value;
          FirePropertyChanged("UserID");
      }
  }

  private string userPW;
  /// <summary>
  /// 사용자 비밀번호
  /// </summary>
  public string UserPW
  {
      get { return userPW; }
      set
      {
          userPW = value;
          FirePropertyChanged("UserPW");
      }
  }

  private string userName;
  /// <summary>
  /// 사용자 이름
  /// </summary>
  public string UserName
  {
      get { return userName; }
      set
      {
          userName = value;
          FirePropertyChanged("UserName");
      }
  }

  private bool isLogin;
  /// <summary>
  /// 로그인 여부
  /// </summary>
  public bool IsLogin
  {
      get { return isLogin; }
      set
      {
          isLogin = value;
          FirePropertyChanged("IsLogin");
      }
  }

...

  /// <summary>
  /// 생성자
  /// </summary>
  public MainPageViewModel()
  {
      //도메인 서비스 솝 클라이언트
      soapClient = new WP7DomainServiceSoapClient();
      //조회 완료 이벤트 처리
      soapClient.GetWP7UriListSetCompleted += new EventHandler<GetWP7UriListSetCompletedEventArgs>(soapClient_GetWP7UriListSetCompleted);
      //저장 완료 이벤트 처리
      soapClient.SubmitChangesCompleted += new EventHandler<NMessenger.WP7ServiceReference.SubmitChangesCompletedEventArgs>(soapClient_SubmitChangesCompleted);

      //인증 서비스 솝 클라이언트
      authClient = new AuthenticationServiceSoapClient();
      authClient.LoginCompleted += new EventHandler<LoginCompletedEventArgs>(authClient_LoginCompleted);
      authClient.LogoutCompleted += new EventHandler<LogoutCompletedEventArgs>(authClient_LogoutCompleted);

  }

  /// <summary>
  /// 로그아웃 완료
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  void authClient_LogoutCompleted(object sender, LogoutCompletedEventArgs e)
  {
      if (e.Error == null)
      {
          var logout = e.Result.LogoutResult.RootResults.FirstOrDefault().DisplayName;
          UserName = "";
          IsLogin = false;
          MessageData = "You have been logged out.";
      }
  }

  /// <summary>
  /// 로그인 완료
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  void authClient_LoginCompleted(object sender, LoginCompletedEventArgs e)
  {
      if (e.Error == null && e.Result.LoginResult.RootResults != null)
      {
          UserName = e.Result.LoginResult.RootResults.FirstOrDefault().DisplayName;
          IsLogin = true;
          MessageData = "Login was successful.";
      }
      else
      {
          MessageData = "Can not login.";
      }
  }

  private ICommand loginCommand;
  /// <summary>
  /// 로그인 커맨드
  /// </summary>
  public ICommand LoginCommand
  {
      get
      {
          if (loginCommand == null)
          {
              loginCommand = new ActionCommand(() =>
              {
                  var login = new LoginRequest(UserID, UserPW, false, null);
                  authClient.LoginAsync(login);
              });
          }
          return loginCommand;
      }
  }

  private ICommand logoutCommand;
  /// <summary>
  /// 로그아웃 커맨드
  /// </summary>
  public ICommand LogoutCommand
  {
      get
      {
          if (logoutCommand == null)
          {
              logoutCommand = new ActionCommand(() =>
              {
                  var logout = new LogoutRequest();
                  authClient.LogoutAsync(logout);
              });
          }
          return logoutCommand;
      }
  }

3. MainPage.xaml은 적당히 수정하고 로그인을 시켜보자

Id : kaki104 , Pw : p@ssw0rd, role : Basic
Id : Administrator, Pw : mvc4dmin, role : administrator

먼저 kaki104로 로그인 해본다.

오른쪽에 로그인 사용자의 이름이 나오면 로그인 된 것이다.
그러면 이번에는 Input User Name에 서버에 등록할 이름을 입력하고 Regist를 눌러보자(이 등록은 채팅을 위한 이름이다. 인증 사용자를 등록하는 것은 아니다.)

kaki라는 이름으로 서버에 등록을 했다. 여기까지는 사실 로그인을 하지 않더라도 할 수 있다. 문제는 Unregist 버튼을 눌러서 서버에서 내 정보를 삭제를 할 경우이다. 이전 포스트에서도 삭제를 하기 위해서는 로그인을 해야 했고, administrator권한을 가지고 있어야지만 삭제가 가능 했었다. 그런 기능을 여기서도 구현을 해보자.

App.xaml.cs

  // Code to execute on Unhandled Exceptions
  private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
  {
      if (System.Diagnostics.Debugger.IsAttached)
      {
          e.Handled = true;
          switch (e.ExceptionObject.GetType().ToString())
          {
              case "FaultException":
                  break;
              default:
                  // An unhandled exception has occurred; break into the debugger
                  System.Diagnostics.Debugger.Break();
                  break;

          }
      }
  }


MainPageViewModel.cs

  private ICommand removeCommand;
  /// <summary>
  /// 삭제 커맨드 - 현재 사용자의 정보를 서버에서 완전히 삭제 한다
  /// </summary>
  public ICommand RemoveCommand
  {
      get
      {
          if (removeCommand == null)
          {
              removeCommand = new ActionCommand(() =>
              {
                  if (CurrentData != null)
                  {
                      //체인지셋 구성
                      ObservableCollection<NMessenger.WP7ServiceReference.ChangeSetEntry> removeItems = new ObservableCollection<NMessenger.WP7ServiceReference.ChangeSetEntry>();
                      NMessenger.WP7ServiceReference.ChangeSetEntry removeChangeSetEntry = new NMessenger.WP7ServiceReference.ChangeSetEntry
                      {
                          OriginalEntity = CurrentData,
                          Entity = CurrentData,
                          Operation = NMessenger.WP7ServiceReference.DomainOperation.Delete
                      };
                      //체인지셋에 수정할 데이터 입력
                      removeItems.Add(removeChangeSetEntry);
                      //SubmitChangeRequest 생성
                      NMessenger.WP7ServiceReference.SubmitChangesRequest request = new NMessenger.WP7ServiceReference.SubmitChangesRequest(removeItems);

                      //인증 쿠키 입력
                      soapClient.CookieContainer = authClient.CookieContainer;

                      //SubmitChangeAsync 실행
                      soapClient.SubmitChangesAsync(request, this);
                  }
              });
          }
          return removeCommand;
      }
  }

...

  /// <summary>
  /// 저장 완료 이벤트 처리
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  void soapClient_SubmitChangesCompleted(object sender, NMessenger.WP7ServiceReference.SubmitChangesCompletedEventArgs e)
  {
      if (e.Error == null)
      {
          //저장 완료 후 반환된 결과를 받아서
          ObservableCollection<NMessenger.WP7ServiceReference.ChangeSetEntry> returnChangeSet = e.Result.SubmitChangesResult;
          //그중 처음 데이터의 오퍼레이션을 확인하고
          var operation = returnChangeSet.FirstOrDefault().Operation;
          //오퍼레이션 종류에 따라 메시지 출력
          switch (operation)
          {
              case NMessenger.WP7ServiceReference.DomainOperation.Insert:
                  MessageData = "My channel uri regist successfully";
                  CurrentData = returnChangeSet.FirstOrDefault().Entity as WP7UriList;
                  break;
              case NMessenger.WP7ServiceReference.DomainOperation.Update:
                  MessageData = "My channel uri modify successfully";
                  break;
              case NMessenger.WP7ServiceReference.DomainOperation.Delete:
                  MessageData = "My channel uri unregist successfully";
                  CurrentData = null;
                  break;
          }
          if (1 == 1)
          {
          }
      }
      else
      {
          MessageData = "The operation failed.[" + e.Error.Message + "]";
      }
  }

이제 Unregist 버튼을 눌러서 확인을 해보자

서버에서 보낸 오류 메시지가 출력된다. 로그인을 하지 않았다면, 로그인이 필요한 서비스라는 메시지가 출력이 되었을 것이다.
Logout 버튼을 눌러서 Logout을 한 후 Administrator로 다시 로그인 한다.

Unregist 버튼을 클릭한다.

삭제가 정상 적으로 처리 되었다는 메시지가 출력된다.

 4. 윈도우 폰에서의 앱에서는
 PC에서 사용하는 거의 대부분의 서비스들을 모두 사용 할 수 있다. 즉, 일반 PC에서 개발해서 사용하는 업무용 프로그램을 윈도우 폰용으로도 충분히 개발을 할 수 있다는 이야기이다. 앞으로는 윈도우 폰의 앱이 단순히 흥미 위주의 앱이 아니라, 업무와 연관된 유용한 앱으로 거듭 태어나기를 바라면서 이번 포스트를 마치겠다.


 

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

원래 계획과는 좀 다르게 메신저가 되어 가고 있지만, 일단 만들어 보기로 했고, 만들어가는 중간 단계이기 때문에 개발자 레퍼런스 정도로 보면 된다. 자세한 설명은 생략한다.

연결 포스트

WCF RIA Service host SOAP endpoint
http://kaki104.tistory.com/63
Silverlight 5 & WCF RIA Service & SQL CE 4.0
http://kaki104.tistory.com/62


1. Service References 추가
ria service가 호스팅 되고 있는 곳의 주소를 가지고 서비스 레퍼런스를 추가한다.



Advanced를 눌러서 Always generate message contracts를 체크 한다. 반환되는 모든 데이터를 ObservableCollection에 담아서 달라는 의미이다.


확인을 눌러서 추가한다.

** 추가 하다가 이런 오류가 나는 경우가 있는데..
Error 6 Custom tool error: Failed to generate code for the service reference 'WP7ServiceReference'.  Please check other error and warning messages for details.

1) 서비스가 실행되고 있지 않는 경우 -> 해당 페이지를 실행 시켜서 서비스를 활성화 시킨 후 다시 시도한다.
2) VS2010 내부에 문제가 있어서 않되는 경우 -> VS2010 종료 후 다시 시작해서 시도한다.

2. 쿠키 사용 추가
How to open a WCF RIA Services application to other type of clients: the SOAP endpoint (3/5)
http://blogs.msdn.com/b/davrous/archive/2010/12/03/how-to-open-a-wcf-ria-services-application-to-other-type-of-clients-the-soap-endpoint-3-5.aspx
위의 포스트 중간 쯤 내려가면 Using the SOAP endpoint from a Windows Phone 7 client 이란 곳이 있다.

그곳에 보면 쿠키 추가하는 부분이 있는데 복사해서 프로젝트의 ServiceReferences.ClientConfig 파일을 열어서 추가한다.

<binding name="BasicHttpBinding_AuthenticationServiceSoap" maxBufferSize="2147483647"
          enableHttpCookieContainer="true"
          maxReceivedMessageSize="2147483647">
    <security mode="None" />
</binding>
<binding name="BasicHttpBinding_BookClubServiceSoap" maxBufferSize="2147483647"
    enableHttpCookieContainer="true"
          maxReceivedMessageSize="2147483647">
    <security mode="None" />
</binding>

** 위의 코드를 추가해 놓으면 Service Reference를 업데이트 하는 도중 오류가 발생한다. 그래서 업데이트를 할때는 살짝 지워 놓고, 업데이트가 끝난 후에 다시 붙여 놓아야 한다. (찾아낸 가장 심플한 대답이였다. ㅋ)
** 쿠키가 있어서 빌드시 오류가 나오는 경우가 있는데..음음..정확한 원인은 아직..그래서 이번 소스에서는 쿠키 제거 했음

3. 윈도우 폰에서 사용하기

  //쿠키 컨테이너
  CookieContainer cookieContainer = null;
  //Soap Client Context
  WP7DomainServiceSoapClient context = null;

...........

  /// <summary>
  /// 생성자
  /// </summary>
  public MainPageViewModel()
  {
      //도메인 서비스 솝 클라이언트
      context = new WP7DomainServiceSoapClient();
      //조회 완료 이벤트 처리
      context.GetWP7UriListSetCompleted += new EventHandler<GetWP7UriListSetCompletedEventArgs>(context_GetWP7UriListSetCompleted);
      //저장 완료 이벤트 처리
      context.SubmitChangesCompleted += new EventHandler<SubmitChangesCompletedEventArgs>(context_SubmitChangesCompleted);
  }

..............


  /// <summary>
  /// 저장 완료 이벤트 처리
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  void context_SubmitChangesCompleted(object sender, SubmitChangesCompletedEventArgs e)
  {
      if (e.Error == null)
      {
          //저장 완료 후 반환된 결과를 받아서
          ObservableCollection<ChangeSetEntry> returnChangeSet = e.Result.SubmitChangesResult;
          //그중 처음 데이터의 오퍼레이션을 확인하고
          var operation = returnChangeSet.FirstOrDefault().Operation;
          //오퍼레이션 종류에 따라 메시지 출력
          switch (operation)
          {
              case DomainOperation.Insert:
                  MessageData = "My channel uri regist successfully";
                  CurrentData = returnChangeSet.FirstOrDefault().Entity as WP7UriList;
                  break;
              case DomainOperation.Update:
                  MessageData = "My channel uri modify successfully";
                  break;
              case DomainOperation.Delete:
                  MessageData = "My channel uri deregist successfully";
                  CurrentData = null;
                  break;
          }
      }
      else
      {
          MessageData = "Error";
      }
  }

................

  //조회 완료시 컬렉션 설정
  void context_GetWP7UriListSetCompleted(object sender, GetWP7UriListSetCompletedEventArgs e)
  {
      WP7Collection = new ObservableCollection<WP7UriList>(e.Result.GetWP7UriListSetResult.RootResults);
  }

...............

  private ICommand addCommand;
  /// <summary>
  /// 추가 커맨드
  /// </summary>
  public ICommand AddCommand
  {
      get
      {
          if (addCommand == null)
          {
              addCommand = new ActionCommand(InputUserName =>
              {
                  //입력 받은 사용자 이름을 확인
                  string userName = InputUserName as string;
                  if (userName != null && userName != "")
                  {
                      //폰의 ChannelUri 확인
                      if (ChannelUri == null && ChannelUri == "")
                      {
                          MessageData = "no chann uri, try again";
                          return;
                      }
                      //새로운 레코드 생성
                      WP7UriList addItem = new WP7UriList
                      {
                          UserName = userName,
                          ChannelUri = ChannelUri,
                          RegDT = DateTime.Now,
                          UptDT = DateTime.Now,
                          UseBT = true
                      };

                      //체인지셋 구성
                      ObservableCollection<ChangeSetEntry> addedItems = new ObservableCollection<ChangeSetEntry>();
                      ChangeSetEntry addChangeSetEntry = new ChangeSetEntry
                      {
                          OriginalEntity = null,
                          Entity = addItem,
                          Operation = DomainOperation.Insert
                      };
                      //체인지셋에 새로만든 레코드 추가
                      addedItems.Add(addChangeSetEntry);
                      //SubmitChangeRequest 생성
                      SubmitChangesRequest request = new SubmitChangesRequest(addedItems);
                      //SubmitChangeAsync 실행
                      context.SubmitChangesAsync(request, this);
                  }
              }
              );
          }
          return addCommand;
      }
  }

kakao 입력 -> Regist 버튼 클릭


실버라이트에서 결과 확인

등록된 kakao를 선택 한 후 Raw Notification 보냄


...................

  private ICommand removeCommand;
  /// <summary>
  /// 삭제 커맨드 - 현재 사용자의 정보를 서버에서 완전히 삭제 한다
  /// </summary>
  public ICommand RemoveCommand
  {
      get
      {
          if (removeCommand == null)
          {
              removeCommand = new ActionCommand(() =>
              {
                  if (CurrentData != null)
                  {
                      //체인지셋 구성
                      ObservableCollection<ChangeSetEntry> removeItems = new ObservableCollection<ChangeSetEntry>();
                      ChangeSetEntry removeChangeSetEntry = new ChangeSetEntry
                      {
                          OriginalEntity = CurrentData,
                          Entity = CurrentData,
                          Operation = DomainOperation.Delete
                      };
                      //체인지셋에 수정할 데이터 입력
                      removeItems.Add(removeChangeSetEntry);
                      //SubmitChangeRequest 생성
                      SubmitChangesRequest request = new SubmitChangesRequest(removeItems);
                      //SubmitChangeAsync 실행
                      soapClient.SubmitChangesAsync(request, this);
                  }
              });
          }
          return removeCommand;
      }
  }

Unregist 버튼 클릭

실버라이트에서 결과 확인


......................

  private ICommand saveCommand;
  /// <summary>
  /// 저장 커맨드 - 현재 사용자의 정보를 업데이트 한다.
  /// </summary>
  public ICommand SaveCommand
  {
      get
      {
          if (saveCommand == null)
          {
              saveCommand = new ActionCommand(() =>
              {
                  if (CurrentData != null)
                  {
                      //현재 데이터 변경
                      CurrentData.UseBT = false;

                      //체인지셋 구성
                      ObservableCollection<ChangeSetEntry> updateItems = new ObservableCollection<ChangeSetEntry>();
                      ChangeSetEntry updateChangeSetEntry = new ChangeSetEntry
                      {
                          OriginalEntity = CurrentData,
                          Entity = CurrentData,
                          Operation = DomainOperation.Update
                      };
                      //체인지셋에 수정할 데이터 입력
                      updateItems.Add(updateChangeSetEntry);
                      //SubmitChangeRequest 생성
                      SubmitChangesRequest request = new SubmitChangesRequest(updateItems);
                      //SubmitChangeAsync 실행
                      soapClient.SubmitChangesAsync(request, this);
                  }
              });
          }
          return saveCommand;
      }
  }

....................

  private ICommand selectCommand;
  /// <summary>
  /// 조회 커맨드 - load button 클릭 시 발생
  /// </summary>
  public ICommand SelectCommand
  {
      get
      {
          if (selectCommand == null)
          {
              selectCommand = new ActionCommand(() =>
              {
                  //조회 함수 호출
                  GetWP7UriListSetRequest request = new GetWP7UriListSetRequest();
                  soapClient.GetWP7UriListSetAsync(request, this);
              });
          }
          return selectCommand;
      }
  }

나머지는 채널 관리하는 소스와, Raw Notification 소스 이다. 이전 포스트에서 이미 다루었던 내용으로 다시 다루지는 않겠다.


 

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

Bing Map Control에 Google Map을 보여주는 예제로, 지도에 Pin을 설정하는 방법까지 작성을 하였다. 그러나, 기본 베이스는 지도 관련 셈플인것 처럼 보이나, 사실 Reactive Extension(Rx)를 실습하기 위한 프로젝트이다.

구글 지도를 Map 컨트롤에 보이도록 하는 작업에 대해서 간단하게 알아보자

1. MainPage.xaml.cs

    //구글맵 설정 클래스
    public class GoogleTile : TileSource
    {
        private int _server;
        private char _mapmode;
        private GoogleTileTypes _tiletypes;

        public GoogleTileTypes TileTypes
        {
            get { return _tiletypes; }
            set
            {
                _tiletypes = value;
                MapMode = MapModeConverter(value);
            }
        }

        public char MapMode
        {
            get { return _mapmode; }
            set { _mapmode = value; }
        }

        public int Server
        {
            get { return _server; }
            set { _server = value; }
        }

        public GoogleTile()
        {
            UriFormat = @"http://mt{0}.google.com/vt/lyrs={1}&z={2}&x={3}&y={4}";
            Server = 0;
        }

        public override Uri GetUri(int x, int y, int zoomLevel)
        {
            if (zoomLevel > 0)
            {
                var Url = string.Format(UriFormat, Server, MapMode, zoomLevel, x, y);
                return new Uri(Url);
            }
            return null;
        }

        private char MapModeConverter(GoogleTileTypes tiletype)
        {
            switch (tiletype)
            {
                case GoogleTileTypes.Hybrid:
                    {
                        return 'y';
                    }
                case GoogleTileTypes.Physical:
                    {
                        return 't';
                    }
                case GoogleTileTypes.Satellite:
                    {
                        return 's';
                    }
                case GoogleTileTypes.Street:
                    {
                        return 'm';
                    }
                case GoogleTileTypes.WaterOverlay:
                    {
                        return 'r';
                    }
            }
            return ' ';
        }
    }

위의 클래스를 만들어 넣은 후 xaml에서 바로 인스턴스 시켜서 Map 컨트롤에 넣으면 끝이다.

2. MainPage.xaml

    <!--Bing Maps Control에 Google 지도를..-->
    <Microsoft_Phone_Controls_Maps:Map Name="googlemap"
                                       CopyrightVisibility="Collapsed"
                                       LogoVisibility="Collapsed"
                                       ScaleVisibility="Visible"
                                       CredentialsProvider="ApBXPZf5IR94SLXE8nh5FYsb5WHKrH1XPY7428-EqQudseivcWhCROIJvGmtnkAV" Grid.Row="2" Grid.ColumnSpan="3">
        <Microsoft_Phone_Controls_Maps:Map.Mode>
            <MSPCMCore:MercatorMode/>
        </Microsoft_Phone_Controls_Maps:Map.Mode>
        <!--구글 지도 클래스를 인스턴스 시켜서 타일 소스에 넣는다.-->
        <Microsoft_Phone_Controls_Maps:MapTileLayer Name="street" Margin="0,0,0,32">
            <Microsoft_Phone_Controls_Maps:MapTileLayer.TileSources>
                <GoogleTileSource:GoogleTile TileTypes="Street"/>
            </Microsoft_Phone_Controls_Maps:MapTileLayer.TileSources>
        </Microsoft_Phone_Controls_Maps:MapTileLayer>
    </Microsoft_Phone_Controls_Maps:Map>

구글 지도에 관한 원본 소스
Google Maps for Windows Phone 7 using Bing Map Control
http://www.codeproject.com/Articles/153467/Google-Maps-for-Windows-Phone-7-using-Bing-Map-Con
를 참조하기 바란다.


3. Reactive Extension(Rx)
지금까지의 프로그램들은 만들어 놓은 함수를 프로세스에 맞추어서 호출해서 사용을 하는 pull 방식의 프로그램이고, Rx는 특정 조건을 만족하는 경우 자동으로 실행을 해서 결과를 만들어서 밀어 보내주는 push 방식의 프로그램을 개발할때 사용된다. 그럼, Rx를 사용하면 어떤 점이 좋은가? 멀티 스레드로 프로그램을 만들어서 더 빠른 실행 속도를 가지게 된다.

여기서 사용된 Rx의 원본 소스
Where am I?
http://compiledexperience.com/windows-phone-7/tutorials/where-am-i


4. 프로젝트에서 사용한 Rx 설명

1) GPS 센서를 이용한 위치 표시를 할 때
    //위치 정보 조회용
    private readonly GeoCoordinateWatcher geoCoordinateWatcher = new GeoCoordinateWatcher();
    ...
    //좌표를 감시하는 변수 - 스테틱
    var observable = CreateObservableGeoPositionWatcher();
    //그 변수의 감시 결과를 구독 - 스테틱
    observable
        .ObserveOn(Scheduler.ThreadPool)
        .ObserveOn(Scheduler.Dispatcher)
        .Subscribe(geo =>
        {
            Latitude.Text = geo.Latitude.ToString();
            Longitude.Text = geo.Longitude.ToString();

            googlemap.Center = geo;
            geoCoordinateWatcher.Stop(); //위치정보 수신 그만~ 계속 받기 위해서는 여기를 막고 아래 변경 사항 적용 소스에는 없으니 추가해서 넣으세요
        });
     ...
    //위치정보 좌표 반환 변수 생성
    private IObservable<GeoCoordinate> CreateObservableGeoPositionWatcher()
    {
        //geoCoordinateWatcher 녀석의 PositionChanged 를 감시 한다.
        var observable = Observable.FromEvent<GeoPositionChangedEventArgs<GeoCoordinate>>(
            e => geoCoordinateWatcher.PositionChanged += e,
            e => geoCoordinateWatcher.PositionChanged -= e)
            .Select(e => e.EventArgs.Position.Location);

        return observable;
    }

    //위치 정보 조회 시작
    //geoCoordinateWatcher.MovementThreshold = 20; //위치정보 수신을 20미터 이동시에 위치정보 변경 이벤트 발생..이것 않하면 1초에 한번씩..;;; 소스에는 없으니 추가해서 넣으세요
   geoCoordinateWatcher.Start();

2) Pin 추가/삭제 작업 시
    //랜덤
    private readonly Random random = new Random();
    //핀 추가 레이어
    MapLayer layer = new MapLayer();
    //핀id
    int pinCount;
    //핀 좌표 컬렉션
    private ObservableCollection<GeoCoordinate> geoCollection = new ObservableCollection<GeoCoordinate>();
    //구독취소 테스트용
    IDisposable one;
    ...
    //핀 좌표 컬렉션 감시 - 스테틱
    Observable
        .FromEvent<NotifyCollectionChangedEventArgs>(geoCollection, "CollectionChanged")
        .ObserveOn(Scheduler.ThreadPool)
        .ObserveOn(Scheduler.Dispatcher)
        .Subscribe(evt =>
        {
            //핀 좌표 변경 시 처리
            switch (evt.EventArgs.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    pinCount++;
                    var location = evt.EventArgs.NewItems[0] as GeoCoordinate;
                    //핀생성
                    Pushpin pin = new Pushpin
                    {
                        Location = location,
                        Content = pinCount.ToString(),
                        Background = location.Speed == 1 ? new SolidColorBrush(Colors.Blue) : new SolidColorBrush(Colors.Red)
                    };
                   
                    layer.Children.Add(pin);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    //핀제거
                    GeoCoordinate ungeo = evt.EventArgs.OldItems[0] as GeoCoordinate;
                    Pushpin unpin = layer
                                    .Children.Cast<Pushpin>()
                                    .SingleOrDefault(
                                        p => p.Location.Latitude == ungeo.Latitude
                                            && p.Location.Longitude == ungeo.Longitude);
                    if (unpin != null)
                    {
                        layer.Children.Remove(unpin);
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    layer.Children.Clear();
                    break;
            }
        });
    //핀 레이어 구글 맵에 추가
    googlemap.Children.Add(layer);
    ...
    //Pin, 1, 2 버튼
    private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        Button ctn = sender as Button;
 
        switch (ctn.Content.ToString())
        {
            case "Pin":
                //현재 위치 Pin만들기
                var pin = new GeoCoordinate { Latitude = Convert.ToDouble(Latitude.Text),
                                              Longitude = Convert.ToDouble(Longitude.Text) };
                geoCollection.Add(pin);
                break;
            case "1":
                //1초에 한번씩 발생 - 현재 스레드
                var tik = Observable
                            .Interval(new TimeSpan(0, 0, 1));
 
                //구독 증명;;
                one = tik.Subscribe(time =>
                        {
                            var latitude = ((random.NextDouble() * 0.26114931) - 0.0) + 37.4;
                            var longitude = ((random.NextDouble() * 0.30761719) - 0.0) + 126.8;
                            var pin2 = new GeoCoordinate
                            {
                                Latitude = Convert.ToDouble(latitude),
                                Longitude = Convert.ToDouble(longitude),
                                Speed = 1
                            };
                            geoCollection.Add(pin2);
                        });
 
                //3초에 한번씩 발생 - 스레드 독립 - 여긴 평생 구독;;
                Observable
                    .Interval(new TimeSpan(0, 0, 3))
                    .ObserveOn(Scheduler.ThreadPool)
                    .ObserveOn(Scheduler.Dispatcher)
                    .Subscribe(time =>
                    {
                        var latitude = ((random.NextDouble() * 0.26114931) - 0.0) + 37.4;
                        var longitude = ((random.NextDouble() * 0.30761719) - 0.0) + 126.8;
                        var pin2 = new GeoCoordinate
                        {
                            Latitude = Convert.ToDouble(latitude),
                            Longitude = Convert.ToDouble(longitude),
                            Speed = 2
                        };
                        geoCollection.Add(pin2);
                    });
 
                //위를 막고 여기를 풀어서 100개를 한번에 추가하는 것도 가능
                //for (int i = 0; i < 100; i++)
                //{
                //    var latitude = ((random.NextDouble() * 0.26114931) - 0.0) + 37.4;
                //    var longitude = ((random.NextDouble() * 0.30761719) - 0.0) + 126.8;
                //    var pin2 = new GeoCoordinate
                //    {
                //        Latitude = Convert.ToDouble(latitude),
                //        Longitude = Convert.ToDouble(longitude)
                //    };
                //    geoCollection.Add(pin2);
                //}
                break;
            case "2":
                //구독취소
                if(one != null)
                    one.Dispose();
 
                //위를 막고 여기를 풀어서 100개를 한번에 지우는 것도 가능
                //for (int i = 0; i < 100; i++)
                //{
                //    geoCollection.Remove(geoCollection.FirstOrDefault());
                //}
                break;
        }
    }


5. Rx를 공부하면 Windows Phone 7 프로그램 개발 할 때 좋은 성능을 낼 수 있다. 구글에서 Reactive Extension로 검색하면 만은 자료를 찾을 수 있으니 검색을 해보기를 권한다.


 

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

윈도우 폰에는 Push Notification이란 서비스가 존재 한다. 혹시, 이 부분에 대해서 잘 모른다면, 여러 자료들을 찾아서 보면 쉽게 이해할 수 있을 것이다. 그런데, 대부분의 예제나 사용하는 것들을 보면 윈도우 폰에서 서버로 채널 Uri를 보내고, 서버에서 MPNS 서버로 Push Notification을 신청을 하는 구조로만 설명이 되어 있다. 왜 윈도우 폰에서 다른 윈도우 폰에게 바로 보낼 수 없을까? 라는 의문을 가지고 시작한 작업이고, 결과는 성공이다.
일단, 이 예제는 자기 자신한테 Push Notification을 보내는 예제이다. 하지만, 다른 윈도우 폰의 channel uri만 알고 있으면 다른 윈도우 폰으로 직접 메세지를 보낼 수 있다.

1. MainPage.xaml

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <StackPanel>
        <TextBlock Text="Input other Uri"/>
        <TextBox x:Name="uri" />
        <TextBlock Text="Send Message"/>
        <TextBox x:Name="tbMessage"/>
        <Button Content="Send Message" Click="Button_Click" />
        <TextBlock Text="Response"/>
        <TextBlock x:Name="tbResponse"/>
        <TextBlock Text="Receive Message"/>
        <TextBlock Name="Receive"/>
    </StackPanel>
</Grid>

2. MainPage.xaml.cs

채널 만드는 부분은 직접 소스를 보기 바란다. 여기서는 Push Notification 작업을 윈도우 폰에서 하는 부분만을 적어 보겠다.

//Self Raw Notification Send
private void SendRawNotification()
{
    uri.Text = channelUri;
    Uri sendUri = new Uri(uri.Text, UriKind.Absolute);

    //Request 생성
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(sendUri);
    //POST로 지정
    request.Method = "POST";
    //뒤에 utf-8을 넣어 주어야 한글도 깨지지 않고 넘어감
    request.ContentType = "text/xml;charset=utf-8";
    //Raw Notification
    request.Headers["X-NotificationClass"] = "3";
   
    request.BeginGetRequestStream(new AsyncCallback(channelRequest), request);
}

//보내는 곳과 연결 하고
private void channelRequest(IAsyncResult asyncResult)
{
    Dispatcher.BeginInvoke(() =>
    {
        HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
        //몸통 만들고
        Stream body = request.EndGetRequestStream(asyncResult);
        //전송할 데이터 만들고
        byte[] contents = Encoding.UTF8.GetBytes(tbMessage.Text);
        //전송
        body.Write(contents, 0, contents.Length);
        body.Flush();
        body.Close();

        request.BeginGetResponse(new AsyncCallback(channelResponse), request);
    });
}

private void channelResponse(IAsyncResult asyncResult)
{
    string notificationStatus = "";
    string notificationChannelStatus = "";
    string deviceConnectionStatus = "";

    var request = (HttpWebRequest)asyncResult.AsyncState;
    var response = request.EndGetResponse(asyncResult);
    //전송 결과 뽑아내고
    notificationStatus = response.Headers["X-NotificationStatus"];
    notificationChannelStatus = response.Headers["X-SubscriptionStatus"];
    deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"];
    //표시해준다
    Dispatcher.BeginInvoke(() =>
    {
        tbResponse.Text = notificationStatus
                            + " " + notificationChannelStatus
                            + " " + deviceConnectionStatus;
    });
}

3. 결과 화면

4. 이제 윈도우 폰끼리 서로 Push Notification 을 주고 받으면서 놀아보자. ㅎ

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

SQL DB 파일을 윈도우 폰에 배포를 해야해서, 찾아보게 되었다. 여기 저기 이것 저것 만은 내용이 있었는데 그 중에 한가지 방법에 대해서 소개 한다.


1. Mango Samples: SQL Toolbox (http://blog.jerrynixon.com/2011/11/in-recent-posts-i-discussed-two-ways-to.html)
포스트에 따르면, 로컬 데이터페이스를 윈도우 폰에 배포하기 위한 방법으로는
1) code-first를 이용해서 격리 저장소에 db를 생성하는 방법
2) SQL Metal을 이용해서 클래스를 만들고 데이터를 만드는 방법
3) SQL Server Compact Toolkit을 이용해서 만드는 방법
이렇게 3가지가 있다고 한다.


2. 여기서는 3)째 방법에 대해서 알아보도록 하자.
일단 SQL SERVER COMPACT TOOLBOX를 설치를 해야한다. Nuget으로 검색해서 설치를 하라고 하는데..Nuget에서는 찾지 못했다. 그래서 직접 다운로드를 받기로 했다.
http://visualstudiogallery.msdn.microsoft.com/0e313dfd-be80-4afb-b5e9-6e74d369f7a1?SRC=VSIDE
다운로드 받을 수 있는 곳이다. 다운로드를 받은 후에 어떤 작업을 해야하는지는 동영상으로 자세히 설명해 주고 있다.


3. 동영상에서 중요한 것은 SqlCE3.5만 가능하다는 것이다. DB를 만들어서 Windows Phone 7 Project에 추가하고,
* SQL Server Compact Toolbox 윈도우를 열고(Tool 메뉴 아래 추가 되어 있다.)

* Add SQL Server Compact 3.5 Connection ( 새로 고침을 해도 뜬다)

* 추가된 Database file에서 오른쪽 마우스를 눌러서 Add Windows Phone DataContext to current Project...을 선택해서
DB 파일의 구조대로 클래스를 만들어 준다.

* 추가된 클래스를 이용해서 Windows Phone 7에서 작업을 하면된다.

4. 블로그 작성자는 요기 까지만 알려줬다. 음 윈도우 폰에서 어떻게 작업을 하는가?
셈플 프로젝을 만들어 봤다.

LocalDB35.sdf라는 파일을 만들고, 한개의 테이블을 추가한 후 4개 정도의 데이터를 입력했다.
그리고, 윈도우 폰 프로젝트에 추가한 후 위의 내용대로 따라서 했더니 LocalDB35Context.cs라는 파일이 Root에 생성됐다.

LocalDB35Context.cs 소스 코드 중 중요한 부분은 바로 아래 코드 이다.

 public static string ConnectionString = "Data Source=isostore:/LocalDB35.sdf";
 
 public static string ConnectionStringReadOnly = "Data Source=appdata:/LocalDB35.sdf;File Mode=Read Only;";
 
윈도우 폰 프로젝트에서 db의 내용을 조회만 할 것이라면 ConnectionStringReadOnly라는 연결 문자열을 사용해서 연결을 하면 되고,  읽기, 쓰기, 수정 등의 작업을 하기 위해서는 ConnectionString이라는 문자열을 사용하면 된다. 그런데, 각 각의 파일 위치가 다른 것을 알 수 있다. 읽기만 하는 경우에는 xap파일에 포함되어 있는 db파일을 바로 연결해서 사용하지만, 수정이 되어야 하는 경우에는 격리 저장소를 이용해야 하는 것이다.

5. MainPage.xaml

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <ListBox x:Name="lbPerson" DisplayMemberPath="Name"/>
        </Grid>
리스트 박스 컨트롤 하나 추가6. MainPage.xaml.cs    public partial class MainPage : PhoneApplicationPage
    {        //읽기 전용
        LocalDB35Context context = new LocalDB35Context(LocalDB35Context.ConnectionStringReadOnly);
 
        // Constructor
        public MainPage()
        {
            InitializeComponent();
 
            lbPerson.ItemsSource = context.Person;
        }
    }

6. 실행실행하면 미리 입력해 놓았던 데이터를 조회해서 화면에 출력하는 것을 알 수 있다.


7. 격리 저장소에서 읽어 오기

    public partial class MainPage : PhoneApplicationPage
    {
        //격리 저장소에서 읽어 오기
        LocalDB35Context context = new LocalDB35Context(LocalDB35Context.ConnectionString);
 
        // Constructor
        public MainPage()
        {
            InitializeComponent();
 
            lbPerson.ItemsSource = context.Person;
        }
    }


8. 실행한다.실행하면 {"The database file cannot be found. Check the path to the database. [ Data Source = \\Applications\\Data\\186AB724-6C44-4F13-8B36-9418B236161B\\Data\\IsolatedStore\\LocalDB35.sdf ]"}이런 오류 메시지를 보게 될 것이다. 왜냐하면, 격리 저장소에 db file을 만드는 작업이 필요한데, 그런 작업을 한적이 없기 때문이다. 위에서 사용한 코드를 수정해 보자.   

public partial class MainPage : PhoneApplicationPage
    {
        //격리 저장소에서 읽어 오기
        LocalDB35Context context = new LocalDB35Context(LocalDB35Context.ConnectionString);
 
        // Constructor
        public MainPage()
        {
            InitializeComponent();
            //격리 저장소에 데이터 베이스가 존재하지 않는 경우 새로 만든다.
            context.CreateIfNotExists();
            lbPerson.ItemsSource = context.Person;
        }
    }

9. 다시 실행


정상적으로 실행 되었다. 그런데 왜 빈 화면일까?  새로만들어진 db에는 데이터가 없기 때문이다. 격리 저장소에 데이터를 넣는 것은 개발자의 몫이다.

격리 저장소에 만들어진 파일은 이전 포스트의 Isolated Storage Explorer를 이용하면 확인이 가능하다.

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

Windows Phone 7 개발을 할 때 격리 저장소(Isolated Storage)에 어떤 내용이 들어가 있고 어떻게 동작이 되는지 알아야 할 필요가 만은데, 어떻게 그 내용을 볼 수 있을까?

Microsoft에서 기본적으로 제공하는 IsolatedStorageExplorerTool이란 프로그램이 존재 한다.(Windos Phone SDK가 설치된 폴더에 있다. 설명 페이지 http://msdn.microsoft.com/en-us/library/hh286408(v=vs.92).aspx)
그러나, 아무래도 Command-Line 상태이고, Product GUID를 입력 해야하는 번거로운 작업이 필요한데, 그 작업을 윈도우에서 바로 확인 할 수 있는 툴이 바로 이 것이다.

일단 이 툴은 개발자만 사용이 가능하다. 왜냐하면, 소스가 있어야지만 그 소스에서 사용되는 격리 저장소의 내용을 볼 수 있기 때문이다.

1. 프로그램 설치
Windows Phone 7 Isolated Storage Explorer beta 페이지로 가서 프로그램을 다운 받아서 설치한다.
http://wp7explorer.codeplex.com/

설치를 하고 나면 바탕 화면에 WP7 Isolated Storage Explorer라는 아이콘이 하나 만들어 지고, 실행을 해보면 아래와 같은 화면이 뜬다.


콤보 박스에도 아무런 내용이 없다. 이제 내 프로그램에서 사용하는 격리 저장소의 내용을 본격적으로 보도록 하자.

2. 윈도우폰 프로젝트를 실행한다.

* Add References -> Browse... -> (위에 프로그램을 설치한 폴더)\Library -> IsolatedStorageExplorer.dll 선택 후 추가

* App.xaml.cs 파일 열기

* 코드 추가 ( localhost라는 곳에 호스트 이름이나 IP를 입력할 수 있다. )
        private void Application_Launching(object sender, LaunchingEventArgs e)
        {
            IsolatedStorageExplorer.Explorer.Start("localhost");
        }

* 코드 추가 2
        private void Application_Activated(object sender, ActivatedEventArgs e)
        {
            IsolatedStorageExplorer.Explorer.RestoreFromTombstone();
        }

여기까지 작업 후 실행 한다.


그러면 Explorer 화면에서 현재 실행 시킨 윈폰 프로그램에서 사용 중인 격리 저장소의 내용을 확인 할 수 있다.
해당 아템을 오른쪽 마우스 클릭해서 내 PC로 다운로드도 가능하다.

추가 사항이 있으면 더 추가 하도록 하겠다.

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

Self Raw Notification  (0) 2012.02.06
Deploy SQL CE 3.5 DB to Windows Phone 7.1  (1) 2012.02.05
Windows Phone 7 Isolated Storage Explorer using  (0) 2012.02.05
Tile Push Notifications Operation  (0) 2012.01.07
Tile create  (0) 2012.01.07
Basic sample  (0) 2012.01.07
블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

윈도우 폰의 푸시 노티피케이션(Push Notifications)은 3가지가 존재하는데, 각각은 서로 장단점을 가지고 있으며, 필요에 따라서 다르게 사용해야 한다.  그 중 타일 노티피케이션(Tile Notifications)은 엡의 메인 타일에 글씨를 출력할 수 있는 것으로 MSDN의 자료들과 몇가지 테스트 해본 내용을 가지고 살펴 보도록 하자.

1. 일단 프로그램을 윈도 폰 에물레이터에 설치를 하고, 프로그램 목록에 가서 마우스로 쿡~찍고 있으면 pin to start라는 메뉴가 나타난다. 그 메뉴를 선택한다.


2. 그러면 시작화면에 프로그램의 Application Tile이 고정되어 있는 것을 확인 할 수 있다.

3. KakiSample 타일을 클릭해서 프로그램으로 들어 온 후에 하단에 + 버튼을 누른다. 그러면 Application Tile의 내용이 변경되어 있는 것을 확인할 수 있다. 이렇게 메인 타일의 내용을 변경 할 수 있으며, 응용하면 세컨드리 타일의 내용도 업데이트가 가능한다.

4. 어플리케이션타일이 변경된 모습

5. 첨부되어있는 ASP.Net 프로젝트를 실행 시킨다. 시작 페이지는 SendTile.aspx 이 화면은 윈도폰에게 타일 노티피케이션 메시지를 작성해서 전달할 페이지이다

6. 엡에서 사용되는 채널 주소를 복사해야한다. vs2010의 Output텝에 들어가서 스크롤 바를 위로 살짝 올리면 아래와 같이 주소를 찾을 수 있다. 그 주소를 복사한다.

7. 복사한 수로를 웹페이지의 Enter URI에 붙여 넣고 나머지 내용도 대충 넣구 맨아래 있는 Send Tile Notification 버튼을 클릭한다. 그러면 잠시 후에  Response에 잘 도착했는지에 대한 결과를 받을 수 있다.

8. 다시 에물레이터로 가서 전송 받은 내용을 확인해 보자

9. 더 자세한 사항은 아래 링크들을 참고 한다.

Push Notifications Overview for Windows Phone
http://msdn.microsoft.com/en-us/library/ff402558(v=VS.92).aspx

A Really Long Post About the Windows Phone 7 Push Notification System
http://www.thisisfanzoo.com/Blog/JeffF/archive/2010/08/02/a-really-long-post-about-the-windows-phone-7-push.aspx

10. MainPage.xaml.cs

using System;
using System.Linq;
using System.Windows;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using Microsoft.Phone.Notification;
using System.Collections.ObjectModel;
using System.Collections.Generic;

namespace WPSample1
{
    public partial class MainPage : PhoneApplicationPage
    {
        //생성자
        public MainPage()
        {
            //푸쉬 채널생성
            HttpNotificationChannel pushChannel;
            //푸쉬 채널이름 설정
            string channelName = "TileSampleChannel";

            InitializeComponent();

            //채널이름 찾아보기
            pushChannel = HttpNotificationChannel.Find(channelName);
            if (pushChannel == null)
            {
                //생성된 채널이 없으면 생성
                pushChannel = new HttpNotificationChannel(channelName);
                //채널정보 업데이트시 이벤트 연결
                pushChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(pushChannel_ChannelUriUpdated);
                //채널 오류 발생 이벤트 연결
                pushChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(pushChannel_ErrorOccurred);
                //채널 열기
                pushChannel.Open();
            }
            else
            {  
                //채널 업데이트 이벤트 연결
                pushChannel.ChannelUriUpdated +=new EventHandler<NotificationChannelUriEventArgs>(pushChannel_ChannelUriUpdated);
                //채널 오류 발생 이벤트 연결
                pushChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(pushChannel_ErrorOccurred);
                //채널 Uri정보 조회
                System.Diagnostics.Debug.WriteLine("[ChannelUri] " + pushChannel.ChannelUri.ToString());
            }

            this.Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("MainPage_Loaded");
        }

        void pushChannel_ErrorOccurred(object sender, NotificationChannelErrorEventArgs e)
        {
            //발생된 에러 메시지 조회
            Dispatcher.BeginInvoke(() =>
            {
                MessageBox.Show(String.Format("푸쉬 노티피케이션 {0} 에러 발생.  {1} ({2}) {3}",
                                    e.ErrorType, e.Message, e.ErrorCode, e.ErrorAdditionalData)
                                    );
            });
        }

        void pushChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
        {
            Dispatcher.BeginInvoke(() =>
            {
                //sender를 푸시체널로 형변환하고
                HttpNotificationChannel pushChannel = sender as HttpNotificationChannel;
                //푸시체널을 어플리케이션타일과 바인딩
                pushChannel.BindToShellTile();

                //변경된 채널 Uri정보 디버그 창에 출력
                System.Diagnostics.Debug.WriteLine("[ChannelUri] " + e.ChannelUri.ToString());
            });
        }

        //하단 툴박스 클릭 이벤트 처리
        private void ApplicationBarIconButton_Click(object sender, System.EventArgs e)
        {
            ApplicationBarIconButton btn = sender as ApplicationBarIconButton;
            if (btn != null)
            {
                //ApplicationTile 가져오기
                var shellTile = ShellTile.ActiveTiles.FirstOrDefault();

                //SecondaryTile 가져오기
                //var shellTile = ShellTile.ActiveTiles
                //    .FirstOrDefault(p => p.NavigationUri.ToString().Contains("MainPage.xaml"));

                //버튼 텍스트를 가지구 어떤 버튼인지 확인
                switch (btn.Text)
                {
                    case "Pin":
                        //SecondaryTile 새로 만들때 사용했던 부분
                        if (shellTile == null)
                        {
                            var tileData = new StandardTileData
                            {
                                Title = "Sample1",
                                Count = 1,
                                BackgroundImage = new Uri("/icons/appbar.sleep.png", UriKind.Relative),
                                BackTitle = "Back Sample1",
                                BackContent = "Back Content",
                                BackBackgroundImage = new Uri("/icons/appbar.sleep.dark.png", UriKind.Relative)
                            };
                            ShellTile.Create(new Uri("/MainPage.xaml", UriKind.Relative), tileData);

                            MessageBox.Show("Pin 작업을 완료했습니다.");
                        }
                        else
                        {
                            //ApplicationTile 업데이트 데이터 생성
                            var tileUpdate = new StandardTileData
                            {
                                Title = "AppTile",
                                Count = 1,
                                BackgroundImage = new Uri("/icons/appbar.sleep.png", UriKind.Relative),
                                BackTitle = "Back AppTile",
                                BackContent = "Back Content",
                                BackBackgroundImage = new Uri("/icons/appbar.sleep.dark.png", UriKind.Relative)
                            };
                            shellTile.Update(tileUpdate);

                            MessageBox.Show("ApplicationTile의 내용을 업데이트 했습니다~");
                        }
                        break;
                    case "Unpin":
                        //지금은 ApplicationTile을 가지고 놀고 있기 때문에 삭제 못함
                        //Unpin버튼이라면 타일이 존재하는 경우에 타일 제거
                        //if (shellTile != null)
                        //{
                        //    shellTile.Delete();
                        //    MessageBox.Show("UnPin 작업을 완료했습니다.");
                        //}
                        break;
                }
            }
        }
    }
}

10. 이런저런 테스트를 해봤는데..Tile Notification은 메인 타일로 바로 업데이트가 되기 때문에 중간에 어떤 노티피케이션이 날라왔는지 확인을 할 수 없는 듯하다..아마도, 메인 타일에 뭔가가 떠있으면 그 타일을 누를거구 누르면 MainPage_Loaded이벤트가 발생하니 그 이벤트에서 먼가 처리하는 것을 넣어 주면 될것 같다. 하지만..그래도 뭔가 날라왔는데..뭐가 날라온것인지 모른다는 건 좀 찝찝하다..그래서 추가적으로 토스트 노티피케이션과 Raw 노티피케이션도 알아보도록 하겠다.


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

Self Raw Notification  (0) 2012.02.06
Deploy SQL CE 3.5 DB to Windows Phone 7.1  (1) 2012.02.05
Windows Phone 7 Isolated Storage Explorer using  (0) 2012.02.05
Tile Push Notifications Operation  (0) 2012.01.07
Tile create  (0) 2012.01.07
Basic sample  (0) 2012.01.07
블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

윈폰의 얼굴 마담이라고 할 수 있는 타일에 대해서 알아 보고 타일을 간단하게 만들고 조작을 해보자. 타일이 되어야 노티피케이션 작업을 타일에 뿌릴 수 있으니..

 

1. 엡에 타일은 기본 타일인 ApplicationTile이 존재하고, 타일을 추가도 할 수 있다.
타일에 대한 자세한 설명은 MSDN을 참고한다.
http://msdn.microsoft.com/en-us/library/hh202948(v=vs.92).aspx
타일 오퍼레이션에 대한 추가 예제는 SDK 예제를 참고한다. Tile Sample
http://msdn.microsoft.com/en-us/library/ff431744(v=vs.92).aspx

 2. WPSample1

하단에 툴바를 살려서 +를 누르면 세컨드리타일을 추가해서 바탕화면에 깔아 놓고, -를 누르면 세컨드리타일을 삭제하는 기능을 구현했다... 무지 간단한 내용이니 한번 보자

3. MainPage.xaml

<phone:PhoneApplicationPage
    x:Class="WPSample1.MainPage"
    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"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="696"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <!--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="kaki Application" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="Sample1 Main" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
          
        </Grid>
    </Grid>
 
    <!--Sample code showing usage of ApplicationBar-->
    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="/icons/appbar.add.rest.png" Text="Pin" Click="ApplicationBarIconButton_Click"/>
            <shell:ApplicationBarIconButton IconUri="/icons/appbar.minus.rest.png" Text="Unpin" Click="ApplicationBarIconButton_Click"/>
            <!--<shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="Pin"/>
                <shell:ApplicationBarMenuItem Text="Unpin"/>
            </shell:ApplicationBar.MenuItems>-->
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>

</phone:PhoneApplicationPage>

4. MainPage.xaml.cs

using System;
using System.Linq;
using System.Windows;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace WPSample1
{
    public partial class MainPage : PhoneApplicationPage
    {
        //생성자
        public MainPage()
        {
            InitializeComponent();
        }

        //하단 툴박스 클릭 이벤트 처리
        private void ApplicationBarIconButton_Click(object sender, System.EventArgs e)
        {
            ApplicationBarIconButton btn = sender as ApplicationBarIconButton;
            if (btn != null)
            {
                //var shellTile = ShellTile.ActiveTiles.FirstOrDefault(); //ApplicationTile

                //에플리케이션의 타일중 SecondaryTile 존재 여부 확인
                var shellTile = ShellTile.ActiveTiles
                    .FirstOrDefault(p => p.NavigationUri.ToString().Contains("MainPage.xaml"));

                //버튼 텍스트를 가지구 어떤 버튼인지 확인
                switch (btn.Text)
                {
                    case "Pin":
                        //Pin버튼이라면 바탕화면에 타일이 존재 하지 않는 경우에만 타일 생성
                        if (shellTile == null)
                        {
                            //타일 생성
                            var tileData = new StandardTileData
                            {
                                Title = "Sample1",
                                Count = 1,
                                BackgroundImage = new Uri("/icons/appbar.sleep.png", UriKind.Relative),
                                BackTitle = "Back Sample1",
                                BackContent = "Back Content",
                                BackBackgroundImage = new Uri("/icons/appbar.sleep.dark.png", UriKind.Relative)
                            };
                            ShellTile.Create(new Uri("/MainPage.xaml", UriKind.Relative), tileData);

                            MessageBox.Show("Pin 작업을 완료했습니다.");
                        }
                        break;
                    case "Unpin":
                        //Unpin버튼이라면 타일이 존재하는 경우에 타일 제거
                        if (shellTile != null)
                        {
                            shellTile.Delete();
                            MessageBox.Show("UnPin 작업을 완료했습니다.");
                        }
                        break;
                }
            }
        }
    }
}

5. 타일에 대한 더 자세한 설명은..
MSDN을 참고 하도록하고, 다음에는 이 타일에 노티피케이션 데이터를 처리하도록 기능을 추가하도록 하겠다.


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

Self Raw Notification  (0) 2012.02.06
Deploy SQL CE 3.5 DB to Windows Phone 7.1  (1) 2012.02.05
Windows Phone 7 Isolated Storage Explorer using  (0) 2012.02.05
Tile Push Notifications Operation  (0) 2012.01.07
Tile create  (0) 2012.01.07
Basic sample  (0) 2012.01.07
블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

티스토리 툴바