안녕하세요.
오늘은 격리된 저장소에 앱관련 설정파일을 생성하고 활용하는 방벙에 대해서 알아 보도록 하겠습니다.
격리된 저장소는 WP7에서 앱관련 파일들의 샌드박스내에서 저장하고 읽을 수 있는 앱을 위한 저장공간인데요.
아래와 같은 구조로 이루어져 있습니다.
파일/폴더를 저장할 수 있을 뿐더러, 어플리케이션의 설정을 보관하는 Local Settings 라는 것이 존재합니다.
이 것을 이용하여 격리된 저장소의 관리할 수 있을 뿐만 아니라, 어플리케이션에서 사용하는 설정값들을 저장하고 불러올 수 있는 기능을 제공합니다.

이제부터는 격리된 저장소에 존재하는 Local Settings 를 활용하는 방법에 대해서 알아보도록 하겠습니다.
사용자 이름이나, 외부와의 통신을 위한 계정이나 주소등을 어플리케이션이 시작될때 마다 사용자에게 직접 입력받아야 한다면, 사용자로썬 불편해서 사용하질 못하겠죠?
이런 부분들은 격리된 저장소에 따로 파일로 저장하여 사용해도 되지만, System.IO.IsolatedStorage.IsolatedStorageSettings 클래스를 이용하면 아주 쉽게 관리하실 수 있습니다.

설정값 저장하기 (기본활용)

우선은 간단한 설정 값을 저장하는 방법에 대해서 알아보도록 하겠습니다.
IsolatedStorageSettings 클래스에 보면 정적 속성으로 ApplicationSettings 속성이 존재합니다.
이 ApplicationSettings 속성을 이용하여 우리가 저장하고자 하는 설정값을 Key/Value 쌍으로 저장이 가능합니다.

새로운 설정값을 추가하고자 한다면
IsolatedStorageSettings.ApplicationSettings.Add("키", 값); 와 같이 추가해주면 되는 것입니다.

사용자 이름을 추가하는 경우라면

IsolatedStorageSettings.ApplicationSettings.Add("userName", "Hong, Gildong");

와 같이 사용 하면 되는 것이죠.

Add메서드는 시그니쳐는 아래와 같습니다.

Add메서드의 시그니쳐를 잘 보면 key는 string 형식이며, value는 object 형식을 받고 있음을 알 수 있습니다.
Value에 해당하는 설정 값이 꼭 문자열일 필요가 없으며, int, DateTime 등의 기본형식외에도 우리가 직접 생성한 클래스의 객체들도 들어 갈 수 있다는 것입니다.

public class User

{

    public string Name { get; set; }

    public string UserID { get; set; }

    public string Password { get; set; }

}


와 같은 클래스가 존재한다고 했을때, 이 클래스에 생성된 객체를 아래처럼 설정값으로 저장할 수도 있다는 의미죠.

User user = new User { Name = "Hong, Gildong", UserID = "Hong", Password = "Gildong1!" };

IsolatedStorageSettings.ApplicationSettings.Add("user", user);


클래스의 인스턴스를 저장할 수 있으므로, 하나의 키에 여러가지 정보를 저장하고 가져올 수 있게 되는 것입니다.

그렇다면, 이미 추가되어 있는 키를 새로 추가하고자 한다면 키가 중복되므로 예외가 발생하겠죠.
기존에 존재하는 키에 대해서 값을 변경할 때는 아래와 같이 ApplicationSettings[] 인덱서를 이용하여 직접 값을 할당 하면 됩니다.

IsolatedStorageSettings.ApplicationSettings["user"] = user;


그리고, 설정이 추가되거나 변경되었을때는 꼭 Save 메서드를 호출 하여 변경분을 Local Settings에 저장하여야 합니다.

IsolatedStorageSettings.ApplicationSettings.Save();


기본적인 저장코드를 전체를 본다면 아래와 같이 되겠죠.

User user = new User { Name = "Hong, Gildong", UserID = "Hong", Password = "Gildong1!" };

 

if (IsolatedStorageSettings.ApplicationSettings.Contains("user"))

{

    IsolatedStorageSettings.ApplicationSettings["user"] = user;

}

else

{

    IsolatedStorageSettings.ApplicationSettings.Add("user", user);

}

 

IsolatedStorageSettings.ApplicationSettings.Save();


ApplicationSettings의 Contains 메서드를 이용하여 현재 설정파일에 user 라는 키값이 존재하는지 확인한후 추가하거나 변경하고, 저장하는 형태를 가지게 됩니다.

설정값 불러오기

설정을 저장하였다면, 값을 불러와야겠죠?
값을 불러 올때는 아래와 같이 사용해도 됩니다.


User user = IsolatedStorageSettings.ApplicationSettings["user"] as User;


User user = (User)IsolatedStorageSettings.ApplicationSettings["user"];


ApplicationSettings 컬렉션은 값을 object 형식으로 가지기 때문에 등록했던 형식으로의 캐스팅이 필요하므로,
두 방법 모두 object 형식을 등록하였던 형식으로 캐스팅을 하고 있습니다.

하지만, 위 방법을 이용하실 때 만일 설정파일에 지정한 키값(user)가 존재하지 않는다면, 예외가 발생합니다.
설정 키값이 존재하는지에 관계없이 설정 값을 가져오는 방법으로 TryGetValue<T>(string key, out object value) 제네릭 메서드가 있습니다.
시그니쳐를 보면

T 는 저장한 값의 형식(여기서는 User 클래스 형식)이며, 설정값을 가져오는데 성공했는지 여부를 bool 형식으로 반환합니다.
그리고, value 는 out 파라미터를 이용합니다. 그러므로 호출시 out 키워드를 꼭 붙어야 합니다.

값을 가져오는 기본적인 코드를 보자면 아래와 같이 될 겁니다.

User user;

IsolatedStorageSettings.ApplicationSettings.TryGetValue<User>("user", out user);

 

if (user != null)

{

    // Do something

}


물론 TryGetValue 메서드는 지정한 키값을 가져오는데 성공했는지 여부를 반환하므로 아래와 같이 사용해도 됩니다.

User user;

if (IsolatedStorageSettings.ApplicationSettings.TryGetValue<User>("user", out user))

{

    // Do something

}


여기 까지만 한다면, 간단한 설정값을 저장하고 불러오는데는 아무런 문제가 없을 것입니다.

설정값 저장하기 (고급활용)

그렇다면, 아래와 같은 클래스를 설정파일로 저장한다고 생각해보죠.

public class Comics

{

    public string FileName { get; set; }

    public string LocalPath { get; set; }

    public DateTime Created { get; set; }

    public long FileSize { get; set; }

    public string ThumnailPath { get; set; }

    public BitmapImage Thumnail { get; set; }

}


List<Comics> comicsList = GetCurrentComics();

 

if (IsolatedStorageSettings.ApplicationSettings.Contains("comics"))

{

    IsolatedStorageSettings.ApplicationSettings["comics"] = comicsList;

}

else

{

    IsolatedStorageSettings.ApplicationSettings.Add("comics", comicsList);

}

 

IsolatedStorageSettings.ApplicationSettings.Save();


위와 같이 하나의 객체만 설정파일에 저장할 수 있는것이 아닌 배열이나, List<T>와 같은 집합형태의 객체도 충분이 설정파일에 저장이 가능합니다.
다수의 리스트를 어플리케이션 설정에 저장해야 할 경우에는 위와 같은 방식을 이용하시면 됩니다.

하지만, 위의 Comics 클래스는 다소 억지스러운 면이 있는 클래스입니다. 뭔가 위화감이 느껴지시나요?
만일, comicsList가 10개의 항목을 가지고 있고, Thumnail 속성의 BitmapImage 객체가 평균적으로 1MB의 크기를 가진다고 가정했을 때, 설정파일의 크기가 최소 크기가 10MB나 될 겁니다.
이렇게 되면 배보다 배꼽이 더 큰 형상이 되겠죠?

앞에서 IsolatedStorageSettings 항목은 값으로 object 형식을 가진다고 하였습니다.
그리고, 이 항목은 XML 형식으로 격리된 저장소에 저장되게되죠. XML은 문자열로 이뤄짐을 모두 잘 알고 계실것입니다.
그렇다면 어떻게 객체가 어떻게 문자열로 저장이 될까요?
바로 객체를 직렬화하여 문자열로 변경하여 저장하게 되는 것입니다.

.NET 3.5 이상에서는 직렬화를 위하여 DataContractAttribute 특성과 DataMemberAttribute 특성을 이용하여 객체의 직렬화를 제어할 수 있습니다.

위 Comics 클래스를 아래와 같이 고쳐보겠습니다.
우선 직렬화를 사용하기 위해서 System.Runtime.Serialization 어셈블리를 프로젝트에 참조로 추가하여 주십시오.
클래스가 존재하는 파일의 using 절에  System.Runtime.Serialization 네임스페이스를 추가하여 주시구요.

[DataContract]

public class Comics

{

    [DataMember]

    public string FileName { get; set; }

    [DataMember]

    public string LocalPath { get; set; }

    [DataMember]

    public DateTime Created { get; set; }

    [DataMember]

    public long FileSize { get; set; }

    [DataMember]

    public string ThumnailPath { get; set; }

    [IgnoreDataMember]

    public BitmapImage Thumnail { get; set; }

}

 


클래스를 위와 같이 수정하였습니다. 클래스에는 DataContractAttribute 특성이 추가되었고, 직렬화할 멤버들에 대해서는 DataMemberAttribute 특성을 추가하였습니다.
그리고, 마지막으로 직렬화에 추가하지 않을 Thumnail 속성에서는 IgnoreDataMemberAttribute 를 추가하여 명시적으로 직렬화에서 제외하였습니다.
참고로 DataContractAttribute를 이용한 직렬화에서는 DataMemberAttribute가 지정되지 않은 멤버들은 모두 직렬화에서 제외됩니다.

List<Comics> comicsList = GetCurrentComics();

 

if (IsolatedStorageSettings.ApplicationSettings.Contains("comics"))

{

    IsolatedStorageSettings.ApplicationSettings["comics"] = comicsList;

}

else

{

    IsolatedStorageSettings.ApplicationSettings.Add("comics", comicsList);

}

 

IsolatedStorageSettings.ApplicationSettings.Save();


와 같이 설정파일에 저장하면, Thumnail 속성을 제외한 객체가 직렬화되어 설정파일에 저장됩니다.

위와 같은 방법을 이용하여 설정파일만을 위한 새로운 클래스를 생성할 필요없이 기존에 사용하던 클래스에서 불필요한 멤버들을 제외하고 설정파일로 저장하는 것이 가능해 지는 것입니다.

오늘은 여기까지 마치도록 하겠습니다.
혹시, 궁금하거나 잘못된 사항이 있으시면 댓글 부탁드리겠습니다.

그럼 즐거운 하루 되십시오.
저작자 표시 동일 조건 변경 허락
Posted by WHiSTLE

트랙백 주소 http://blog.ntils.com/trackback/63 관련글 쓰기

댓글을 달아 주세요

  1. 2010/12/14 20:43

    글 정말 잘 보았습니다.
    음 제가 ObservableCollection 을 이용해서 리스트 박스에서 바로 추가 삭제 되는 식으로 하고 있는데요
    이걸 isolated storage에 적용시키고 싶은데 좀 이해가 안되네요 이것 저것 해보고는 있는데 이런쪽으로는 워낙 초보라...

  2. 2011/12/11 21:33

    정말 잘보았습니다. IsolatedStorageSettings 부분을 이해하는데 큰도움이 되었네요 ~ 감사합니다.

지난번 Windows Phone 7 에서 압축파일을 읽는 방법에 대한 글을 포스팅한 적이 있습니다. (보지 못하신 분은 [Windows Phone 7] - Windows Phone 7 에서 ZIP 압축파일 읽기 를 보시면 되겠습니다.)
압축파일을 프로젝트에 추가해서 GetResourceStream 메서드를 이용하여 압축된 파일에 대한 Stream 객체를 얻고,
그 Stream 객체를 이용하여 ZipFile 클래스의 인스턴스를 생성하였는데요.

이번에 프로젝트 구현시에 격리된 저장소(Isolated Storage)에 저장된 파일을 IsolatedStorageFileStream 형태로 읽어서, 그 Stream을 ZipFile 클래스의 인스턴스 생성시 이용하고자 하니, 문제가 발생하였습니다.
SharpZipLib에서 따로 오류는 발생하지 않으나, 객체를 생성하는 과정에서 프로그램이 응답없음 상태로 빠지고 말더군요.

고민하다가, IsolatedStorageFileStream을 MemoryStream으로 옮겨서, 이 MemoryStream 을 이용하여 ZipFile 클래스의 인스턴스를 생성하는 방식으로 문제를 해결하였습니다.

코드는 아래와 같습니다.

System.IO.IsolatedStorage.IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();

 

string localPath = "SamplePictures.zip";

 

using (MemoryStream mStream = new MemoryStream())

{

    using (IsolatedStorageFileStream isfStream = new IsolatedStorageFileStream(localPath, FileMode.Open, isf))

    {

        int bufferSize = 40 * 1024;

        byte[] buffer = new byte[bufferSize];

        int readBytes = 0;

 

        do

        {

            readBytes = isfStream.Read(buffer, 0, bufferSize);

            mStream.Write(buffer, 0, readBytes);

 

        } while (readBytes > 0);

    }

 

    ZipFile zipFile = new ZipFile(mStream);

}


코드에서 보면 격리된 저장소에 있는 SamplePictures.zip 파일을 IsolatedStorageFileStream 을 이용하여 읽어 MemoryStream으로 옮겨서 그 MemoryStream을 이용하여 ZipFile 객체를 생성하고 있습니다.

위와 같이 하시면, 격리된 저장소에 있는 압축파일(Zip파일)도 문제없이 사용하실 수 있습니다.
저작자 표시 동일 조건 변경 허락
Posted by WHiSTLE

트랙백 주소 http://blog.ntils.com/trackback/62 관련글 쓰기

댓글을 달아 주세요

새로운 한주의 시작은 즐겁게 하고 계신가요?
이번에는 Windows Phone 7 의 테마 색상을 알아오는 방법에 대해서 알아보도록 하겠습니다.
Windows Phone 7 에서 테마의 설정은 Settings - theme 메뉴를 통해서 가능한데요. 에뮬레이터에서도 가능하니 한번 해보세요.
제공되는 테마는 dark (검은색 바탕의 테마) 와 light (하얀색 바탕의 테마) 가 있구요.
거기에다 타일의 색상 등을 결정하는 Accent color 를 설정하는 부분이 있습니다.

WP7 어플리케이션을 작성할 때 사용자에게 일관된 사용자 경험을 제공하기 위해서 사용자가 직접 선택한 테마를 잘 활용하는 것도 중요할 수 있는데요.
테마의 색상을 알아오는 부분은 http://devlicio.us/blogs/derik_whittaker/archive/2010/07/27/how-to-detect-the-theme-being-used-in-wp7.aspx 여기서 알 수 있었구요.
액센트 색상은 어떻게 알아올 수 있을까 생각해보았습니다.

위 링크된 포스트에서와 같이 테마의 이름을 직접 가져오기는 힘든것 같구요. 현재 폰의 ForegroundColor 또는 BackgroundColor를 이용하여 테마를 유추해내는 방법을 사용할 수 있습니다.

Color themeColor = (Color)Application.Current.Resources["PhoneForegroundColor"];

 

if (themeColor.ToString() == "#FFFFFFFF")

{

    this.PageTitle.Text = "Dark";

}

else if (themeColor.ToString() == "#DE000000")

{

    this.PageTitle.Text = "Light";

}


현재 어플리케이션 리소스 중 글자색상을 나타내는 PhoneForegroundColor 리소스를 가져와서, 그 색상이 흰색이라면 테마는 Dark 이고, 색상의 검은색이라면 테마는 Light 입니다.

물론, PhoneBackgroundColor 를 이용하는 방법도 있습니다. 위와 동일한 방법이지만, 배경색은 색상자체가 테마를 나타내는 거죠.

themeColor = (Color)Application.Current.Resources["PhoneBackgroundColor"];

 

if (themeColor.ToString() == "#FF000000")

{

    this.PageTitle.Text = "Dark";

}

else if (themeColor.ToString() == "#FFFFFFFF")

{

    this.PageTitle.Text = "Light";

}


그리고, 액센트 색상을 가져오기 위해서는 PhoneAccentColor 리소스를 이용하시면 됩니다.

Color accentColor = (Color)Application.Current.Resources["PhoneAccentColor"];

 

string accent = null;

 

switch (accentColor.ToString())

{

    case "#FFFF0097":

        accent = "magenta";

        break;

    case "#FFA200FF":

        accent = "purple";

        break;

    case "#FF00ABA9":

        accent = "teal";

        break;

    case "#FF8CBF26":

        accent = "lime";

        break;

    case "#FFA05000":

        accent = "brown";

        break;

    case "#FFE671B8":

        accent = "pink";

        break;

    case "#FFF09609":

        accent = "orange";

        break;

    case "#FF1BA1E2":

        accent = "blue";

        break;

    case "#FFE51400":

        accent = "red";

        break;

    case "#FF339933":

        accent = "green";

        break

}

 

this.PageTitle.Foreground = new SolidColorBrush(accentColor);

this.ApplicationTitle.Text = accent;


위 코드와 같이 현재는 윈도폰 테마에 대해서 직접적으로 알아 올 수 있는 방법은 없는듯 하지만, 위와 같이 약간의 코드를 이용하여 테마 및 액센트 색상을 가져올 수 있습니다.

끝까지 읽어주셔서 감사합니다.
즐거운 하루 되십시오.
저작자 표시 동일 조건 변경 허락
Posted by WHiSTLE

트랙백 주소 http://blog.ntils.com/trackback/60 관련글 쓰기

댓글을 달아 주세요

이번에는 Windows Phone 7에서 압축파일에서 개별 파일을 읽는 방법에 대해서 알아보고자 합니다.
Silverlight 기반의 Windows Phone 7 응용프로그램의 배포에 사용되는 XAP 파일이 ZIP형식의 압축파일입니다.
C# 코드가 컴파일되어 있는 DLL과 XAML 파일, 리소스들이 이 압축파일에 포함되어 있죠.
그러니, 분명히 Windows Phone에서는 ZIP 압축파일을 해제하는 방법이 분명히 존재합니다.
하지만, 그 부분은 제공하는 API가 아무래도 좀 빈약하고, ZIP의 압축이나 압축해제의 용도로만 사용되는 것이 아니다 보니, 용도에 딱 들어맞지 않을 수 밖에 없습니다.

그래서, 오픈소스로 제공되고 있는 SharpZipLib을 이용하는 방법이 없을까하고 생각하던 중에, Nick's .NET Travels :: Windows Phone 7 Data : Json WCF Data Service with IIS7 Compression 이라는 블로그 글에서 SharpZipLib을 WP7 용으로 컨버팅해서 제공하는 발견하고 얼른 받아서 구현해봤습니다.

SharpZipLib for Windows Phone : 다운로드

이번 샘플은 다수의 이미지 파일을 포함한 압축파일(ZIP)에서 이미지 파일을 추출하여 보여주는 형태로 만들었습니다.

아래 샘플소스는 Windows Phone Developer Tools Beta 를 이용하여 만들어 졌으며, RTM 버전에서의 테스트도 완료되었습니다.


압축된 이미지파일 보여주기 샘플 구현
  1. 새로운 Windows Phone Application 프로젝트를 생성합니다.
  2. Solution Explorer에서 솔루션을 선택하고 Add - Existing Project...를 선택하여 다운로드 받은 파일에서 wpICSharpCode.SharpZLib.csproj 를 선택하여 프로젝트를 추가합니다.
  3. Windows Phone Application 프로젝트에서 Add Reference 를 선택하고, 추가한 Projects 탭에서 wpICSharpCode.SharpZLib 을 추가해 주세요.

  4. 이미지 파일들로 구성된 압축파일을 Window Phone Application 프로젝트에 추가하고, Properties 창에서 Build ActionContent 로 변경하여 주십시오.


  5. MainPage.xaml 의 UI는 아래와 같이 구성하였습니다.


  6. 이제 PhoneApplicationPage 의 Loaded 이벤트와 prevButton 그리고 nextButton 버튼의 Clicked 이벤트에 각각의 이벤트 핸들러를 등록하여 주십시오.
  7. 클래스 멤버변수로 압축파일의 정보를 보관할 ISharpCode.SharpzipLib.Zip 네임스페이스의 ZipFile 클래스와 현재 이미지 파일의 인덱스를 관리할 Int32형식의 변수를 선언합니다.


    public
    partial class MainPage : PhoneApplicationPage

    {

        ZipFile _zipfile = null;

        int _fileIndex = -1;


  8. 페이지가 로드되면 리소스 파일로 첨부한 SamplePictures.zip 파일을 이용하여 ZipFile 클래스의 인스턴스를 생성하고 로드시에 첫 페이지를 출력할 수 있도록 합니다.

    private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)

    {

        StreamResourceInfo zipStreamResource = Application.GetResourceStream(
                                                  new
    Uri("SamplePictures.zip", UriKind.Relative));

        _zipfile = new ZipFile(zipStreamResource.Stream);

     

        MoveNextImage();

    }


    위 코드에서 ZipFile 클래스의 인스턴스를 생성하기 위하여 Stream 을 이용하는 것을 알 수 있습니다. 우리는 압축파일을 리소스 형태로 추가하였으므로, StreamResourceInfo 객체에서 Stream 객체를 획득할 수 있습니다.
    만일, 격리된 저장소에 파일이 존재한다면, 이전 포스트인 [Windows Phone 7] - 웹에서 Windows Phone 7의 격리된 저장소로 파일 다운로드 받기 에 격리된 저장소에서 파일 읽는 방법이 구현되어 있으므로 참고하시기 바랍니다.

  9. 가장 핵심이라고 할 수 있는 압축(ZIP)파일에서 이미지를 읽어오는 코드를 포함할 ViewImage라는 메서드를 생성하고 아래와 같이 구현합니다.

    private void ViewImage()

    {

        ZipEntry entry = _zipfile[_fileIndex];

        String entryFileName = entry.Name;

        using (Stream zipStream = _zipfile.GetInputStream(entry))

        {

            byte[] buffer = new byte[4096];

            int readBytes;

            using (MemoryStream mStream = new MemoryStream())

            {

                do

                {

                    readBytes = zipStream.Read(buffer, 0, buffer.Length);

                    mStream.Write(buffer, 0, readBytes);

     

                } while (readBytes > 0);

     

                BitmapImage image = new BitmapImage();

                image.SetSource(mStream);

                contentImage.Source = image;

     

                mStream.Close();

            }

     

            zipStream.Close();

        }

    }


    ZipFile 객체에서 현재 인덱스의 파일을 가져오면 ZipEntry 클래스의 객체를 가져올 수 있으며, 이 ZipEntry 객체의 GetInputStream을 이용하여 압축되어 있는 파일의 스트림을 획득할 수 있습니다.
    하지만, 주의할 점은 현재 획득한 스트림을 이용하여 바로 BitmapImage 객체를 생성할 수가 없습니다.
    이를 위해 코드에서는 MemoryStream에 압축 파일의 스트림을 기록하여 BitmapImage 객체를 생성하고 있습니다.
    그리고, 생성한 BitmapImage 객체를 contentImage 컨트롤에 소스로 등록하여 이미지를 출력합니다.

  10. 압축 파일에는 다수의 이미지가 존재하므로 전후로 이동하기 위해서 아래와 같은 코드를 등록합니다.

    private void MoveNextImage()

    {

        if (_fileIndex + 1 < _zipfile.Count)

        {

            _fileIndex++;

            ViewImage();

        }

    }

     

    private void MovePrevImage()

    {

        if (_fileIndex - 1 >= 0)

        {

            _fileIndex--;

            ViewImage();

        }

    }


    ZipFile 객체에서 Count 속성을 통해 압축파일내에 있는 파일의 갯수를 알 수 있습니다

  11. 마지막으로 prevButton 와 nextButton 버튼 컨트롤의 Click 이벤트 핸들러를 추가합니다.

    private void nextButton_Click(object sender, RoutedEventArgs e)

    {

        MoveNextImage();

    }

     

    private void prevButton_Click(object sender, RoutedEventArgs e)

    {

        MovePrevImage();

    }


  12. 프로젝트를 실행하면 압축파일에 포함된 이미지가 출력됨을 알 수 있습니다.
    또한 버튼을 이용하여 다음 이미지 및 이전 이미지로의 이동이 가능합니다.
만일 실행 시에 PlatformNotSupportedException 이 발생한다면, wpICSharpCode.SharpZLib 프로젝트에서 Zip 폴더내의 ZipConstraints.cs 파일에서


Encoding.GetEncoding(DefaultCodePage)


부분을 아래와 같이 변경하여 주십시오.


Encoding.UTF8


이는 현재 Windows Phone 7 에서는 Encoding 이 UTF-8 만을 지원하므로, 다른 형식의 인코딩을 이용하고자 하여 발생하는 문제입니다.
한국어가 지원되는 Windows Phone 7 이 나올때 쯤이면 이 문제도 해결되지 않을까 생각합니다.


샘플 프로젝트 파일을 따로 올렸으니 참고하시기 바랍니다.
첨부된 파일에는 업로드 용량관계로 압축파일이 포함되어 있지 않으니, 압축파일을 추가하여 사용하시면 됩니다.

격리된 저장소에서 Zip파일을 읽어 오실경우에는 [Windows Phone 7] - Windows Phone 7에서 격리된 저장소의 압축파일을 SharpZipLib 활용하여 읽기(AS) 를 확인하여 주십시오.


끝까지 읽어 주셔서 감사합니다.

그럼 좋은 하루 되십시오.
저작자 표시 동일 조건 변경 허락
Posted by WHiSTLE

트랙백 주소 http://blog.ntils.com/trackback/59 관련글 쓰기

댓글을 달아 주세요

오늘은 Windows Phone에 기본적으로 사용되는 폰트 외에 직접 추가한 폰트를 사용하는 방법에 대해서 알아볼까합니다.

우선 WP7은 타이포그라피가 UX의 핵심에 있는 METRO라는 디자인컨셉을 가지고 있다보니, 기본 폰트가 상당히 예쁩니다.

그냥 사용하더라도 아무런 불만이 없을 정도죠. (참고로 WP7에서 사용되는 기본 폰트는 Segeo WP 라는 폰트로 Microsoft에서 직접 만든 폰트라고 합니다.)

하지만 세상이라는게 그렇습니까? 자신만의 폰트로 개성을 표현하고 싶어질 것입니다.

불행하게도 WP7은 샌드박스모델을 사용하다 보니, 어플리케이션이 시스템 폴더에 접근하는 것이 불가능합니다.

즉, 어플리케이션이 새로운 폰트를 시스템에 설치할 수 가 없는것이죠.

하지만, 너무 실망할 필요는 없습니다. 어플리케이션에서 사용할 폰트를 해당 어플리케이션의 리소스로 등록하여,

그 폰트를 불러와 사용한다면 적어도 내 어플리케이션 안에서 만큼은 기본 폰트 외의 사용자 지정 폰트를 사용하는 것이 가능합니다.

이번에는 어플리케이션에 폰트를 리소스로 등록하여 놓고, 그 폰트를 사용하는 간단한 샘플 어플리케이션을 만들어 보도록 하겠습니다. 


Visual Studio 에서의 사용자 지정 폰트 사용하기


  1. Visual Studio에서 Silverlight Application for Winodws Phone 프로젝트를 생성해 주십시오.
  2. 솔루션 탐색기에서 프로젝트 내에 Fonts 라는 폴더를 생성하세요.
  3. 사용하고자 하는 폰트를 Fonts 폴더에 추가해주시고, 속성창에서 각 폰트파일의 Build ActionContent로 변경하여 주세요. (여기서는 Hawaii Killer 라는 폰트와 Scriptina 라는 폰트를 추가하였습니다.)

  4. Mainpage.xaml 의 UI 구성은 아래와 같이 구성을 하였습니다.

  5. Mainpage.xaml 에서 PageTitle TextBlock의 XAML코드에 아래 내용을 추가하여 주십시오.
    FontFamily="Fonts\SCRIPTIN.ttf#Scriptina"
    이 부분이 XAML 코드에서 사용자 지정 폰트를 사용하도록 지정하는 부분입니다.
    일반적으로 FontFamily속성은 설치된 폰트의 폰트패밀리의 이름을 지정하여 사용하지만, 리소스로 추가된 폰트를 사용하고자 한다면 폰트파일경로#폰트패밀리명 과 같은 형식으로 사용하실 수 있습니다.
    위에서는 폰트경로가 Fonts\SCRIPTIN.ttf 이고 폰트패밀리가 Scriptina 가 되는 것입니다.
    파일명과 폰트패밀리명은 다를 수 있으므로, 주의해주십시오.

  6. XAML 코드에서 직접지정하여 폰트를 사용하실 수 도 있지만, 상황에 따라 폰트를 사용해야 한다면 코드 상에서 폰트를 로드하여 사용하여야 할텐데요.
    이때는 Control 의 FontSource 속성과 FontFamily 속성을 이용하여 사용자 지정 폰트를 사용할 수 있습니다.
    추가했던 fontChangeButton 버튼 컨트롤에 Click 이벤트 핸들러를 등록합니다.
    코드 비하인트 파일인 MainPage.xaml.cs 파일에서 fontChangeButton의 Click 이벤트 핸들러 메서드에 아래와 같은 코드를 추가하여 주십시오.

    StreamResourceInfo fontResourceInfo = Application.GetResourceStream(new Uri("Fonts/Hawaii_Killer.ttf", UriKind.Relative));

    FontSource fontSource = new FontSource(fontResourceInfo.Stream);

    customFontTextBlock.FontSource = fontSource;

    customFontTextBlock.FontFamily = new FontFamily("Hawaii Killer");


    코드를 보자면 Bulid Action 이 Content로 지정된 리소스 파일을 Application 클래스의 GetResourceStream 메서드를 이용하여 가져오고, 가져온 리소스 스트림을 이용하여 FontSource 객체를 생성합니다.
    TextBlock 객체인 customFontTextBlock의 FontSource 속성에 생성한 FontSource를 설정하여, 사용자 지정 폰트를 사용할 수 있도록 설정합니다.
    그리고는 마지막으로는 사용할 폰트 패밀리명을 FontFamily 속성에 설정하여 줍니다.
    FontSource 만 설정하고 FontFamily를 설정하지 않으면 적용되지 않으니, 꼭 FontFamily 도 설정하여 주세요.
    여기까지의 결과가 XAML 코드에서 FontFamily="Fonts\Hawaii_Killer.ttf#Hawaii Killer" 와 동일한 기능을 하게됩니다.

  7. 이제 프로젝트를 실행하면, 아래와 같은 화면이 출력됩니다.

    처음 로드가 완료된 화면에서는 XAML 코드에서 지정된 PageTitle TextBlock의 폰트가 변경되었음을 알 수 있으며,
    Change Font 버튼을 클릭하여 기본 폰트로 출력되던 텍스트가 새로운 폰트로 변경되었음을 두번째 화면에서 보실 수 있습니다.

참고하실 점은 아직까지는 한글 폰트에 대한 적용은 완전하지 않은 듯 합니다.
한글 폰트로 테스트해보았을때는 폰트의 적용이 제대로 되지 않았는데, 영문폰트로 교체하니 제대로 적용이되네요.
정식판이 나오면 수정되지 않을까하는 막연한 기대를 가져봅니다.

이번 글을 여기까지 입니다.
프로젝트 소스는 따로 첨부하여 드리니, 필요하신 분은 참고하시구요.
수고하셨구요. 끝까지 읽어주셔서 감사합니다.

저작자 표시 동일 조건 변경 허락
Posted by WHiSTLE

트랙백 주소 http://blog.ntils.com/trackback/58 관련글 쓰기

댓글을 달아 주세요

이번 글은 네이버 윈도우폰 개발자 모임에서 진행중인 Windows Phone 7 스터디에서 원격지(웹)상에 존재하는 파일을 WP7에 어떻게 저장하는가에 대한 궁금증이 생겨서 구현해보고 작성하는 블로그 포스트입니다.

Windows Phone 7 은 샌드박스 모델을 사용하는 관계로 시스템의 저장공간을 마음대로 사용할 수가 없습니다.

WP7 어플리케이션은 각 어플리케이션 마다 격리된 저장소를 할당 받고, 그 격리된 저장소 내의 파일들에 대해서만 읽고 쓰기가 가능합니다. 물론, 각종 미디어 파일(비디오, 사진 등)은 공용 스토리지를 이용하여 사용할 수 있습니다.


아래 구현된 소스는 Windows Phone 7 Developer Tools Beta 버전으로 구현되었습니다.


샘플 소스의 개요
  • 샘플 소스에는 파일 다운로드를 위한 테스트용 웹 응용프로그램 프로젝트(ASP.NET MVC 2) 가 존재하며,
    이 프로젝트를 이용하여 WP7에서 파일 다운로드를 구현합니다.
  • Windows Phone Application 프로젝트에서는 위 웹 응용프로그램에서 파일을 다운로드하여, 어플리케이션에 할당된 격리된 저장소에 파일을 저장하고, 이후 격리된 저장소에 저장된 파일들의 목록을 보여줍니다.
  • 저장소에 저장된 파일의 리스트에서 항목을 선택하면, TXT 파일일 경우에는 그 내용을 TextBlock에 출력합니다.

다운로드를 위한 테스트용 웹사이트의 구현

웹사이트는 단지 다운로드 기능만이 있으면 되므로, /Download 경로로 접속시에 지정한 파일을 전송해주는 기능만을 구현합니다.
  1. Visual Studio 2010 에서 새로운 ASP.NET MVC 2 Empty Web Application 프로젝트를 생성합니다.
  2. 웹 응용프로그램 프로젝트 루트 경로에 Files 라는 이름의 폴더를 추가하고, WP7으로 전송할 파일을 추가합니다.
    여기서는 ibatis.licence.txt 라는 파일을 추가하였습니다.
  3. Controllers 폴더에 DownloadController라는 새로운 컨트롤러 클래스를 추가합니다.

  4. DownloadController 클래스에는 아래와 같은 코드를 추가합니다.

    private void downloadButton_Click(object sender, RoutedEventArgs e)
    {
        string fileUrl = "http://localhost:2975/Download"; // 파일 다운로드를 위한 URL 경로   
        HttpWebRequest request = HttpWebRequest.Create(fileUrl) as HttpWebRequest;
        request.BeginGetResponse(new AsyncCallback(DownloadCallback), request);
    }

  5. Properties 폴더를 더블클릭하거나, 프로젝트의 속성을 선택하여 Web 탭에서 항상 동일한 포트를 이용하도록 설정하여 줍니다.

  6. 웹 프로젝트를 실행하고 /Download 경로로 접속하여 파일이 다운로드가 되는지 확인하여 주세요.

Window Phone 7 에서 웹 다운로드 구현
  1. Visual Studio 2010 Express for Windows Phone 이나 Visual Studio 2010 에서 Silverlight for Windows Phone 프로젝트 템플릿 그룹에서 Windows Phone Application 을 선택하여 새로운 프로젝트를 생성합니다.
  2. MainPage.xaml 페이지의 UI는 아래와 같이 구현하였습니다.

  3. downloadButtonClick 이벤트에 대한 이벤트 핸들러를 등록하고, 코드 비하인드 파일에서 아래와 같은 코드를 추가하여 주세요.

    private void downloadButton_Click(object sender, RoutedEventArgs e)
    {
        string fileUrl = "http://localhost:2975/Download"; // 파일 다운로드를 위한 URL 경로   
        HttpWebRequest request = HttpWebRequest.Create(fileUrl) as HttpWebRequest;
        request.BeginGetResponse(new AsyncCallback(DownloadCallback), request);
    }


    위 코드에서는 파일을 다운로드 받기 위해서 HttpWebRequest 클래스를 이용하도록 하였습니다.
    주의할 점은 Silverlight 는 HttpWebRequest 의 동기방식의 호출을 지원하지 않습니다.
    그러므로, HttpWebRequest 객체에서 HttpWebResponse 객체를 얻기위해서는 BeginGetResponse 메서드와 EndGetResponse 메서드를 이용하여 비동기 방식으로 HttpWebResponse 객체를 가져와야 합니다.

  4. HttpWebResponse 객체를 얻기 위하여 BeginGetResponse 메서드를 호출하였으니, HTTP 요청이 완료된 시점에 실행할 DownloadCallback 메서드를 구현하여야 합니다.
    위 코드에서 BeginGetResponse 메서드 호출시에 콜백메서드로 DownloadCallback 이라는 이름의 메서드를 지정하였음을 보실 수 있을 겁니다. 이 메서드는 아래와 같이 구현합니다.

    private void DownloadCallback(IAsyncResult asyncronousResult)
    {
        // BeginGetResponse 메서드의 state 매개변수로부터 전달받은 HttpWebRequest객체의
        // EndGetResponse 메서드를 호출하여 HttpWebResponse 객체를 획득한다.
        HttpWebRequest request = (HttpWebRequest)asyncronousResult.AsyncState; 
        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncronousResult);

        // 저장할 파일명을 가져옵니다.
        string filename = GetFileName(response.Headers["Content-Disposition"]);

        try
        {
            // HttpWebResponse의 스트림을 이용하여 격리된 저장소에 파일을 쓴다.
            using (Stream stream = response.GetResponseStream())
            {
                IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication();
                using (IsolatedStorageFileStream fileStream =
                        new IsolatedStorageFileStream(filename, FileMode.OpenOrCreate, appStorage))
                {
                    byte[] buffer = new byte[32768];
                    int readBytes;

                    while ((readBytes = stream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        fileStream.Write(buffer, 0, readBytes);
                    }
                }
            }
        }
        catch (Exception) { }

        // UI Thread에 접근하려고 할 경우에는 UnauthorizedAccessException이 발생한다.
        this.Dispatcher.BeginInvoke(delegate()
        {
            DisplayFiles();
        });
    }

    BeginGetResponse 메서드를 호출할 때 두번째 매개변수로 생성하였던 HttpWebRequest 객체를 전달하였으므로, 콜백메서드에서는 IAsyncResult 매개변수에서 AsyncState 속성을 통하여 전달하였던 HttpWebRequest 객체를 획득할 수 있습니다.
    이제, HttpWebRequest 객체에서 EndGetResponse 메서드를 호출하여 웹 요청에 대한 응답인 HttpWebResponse 객체를 가져올 수 있습니다.

    이제 다운로드 받은 파일을 WP7 로컬 스토리지에 파일을 저장하여야 합니다.
    서두에 말씀드렸다시피, WP7에서는 각 어플리케이션에 할당된 격리된 저장소로의 파일 저장 및 읽기를 지원하므로, 다운로드 받은 파일은 격리된 저장소에 저장하여야 합니다.
    Silverlight에서는 격리된 저장소에 접근하기 위해 IsolatedStorageFile 클래스를 제공하고 있습니다.
    IsolateStorageFile 클래스의 정적메서드인 GetUserStoreForApplication 메서드를 이용하면, 현재 어플리케이션에 할당된 격리된 저장소의 정보를 가져올 수 있습니다.
    그리고, 웹에서 다운로드한 파일을 저장하기 위하여 코드에서는 IsolateStorageFileStream을 이용하여 저장소에 파일을 저장하고 있습니다.

    마지막 부분에서는 파일의 저장이 완료되면 ListBox에 다운로드 받은 파일의 리스트를 출력하기 위하여 DisplayFiles 메서드를 호출하고 있습니다.
    여기서 만일 바로 DisplayFiles 메서드를 호출하게 된다면, UnauthorizedAccessException이 발생하게 됩니다.
    이는 지금 실행되고 있는 DownloadCallback 메서드는 비동기 호출로 인하여 UI 스레드인 메인스레드와는 다른  스레드에서 실행되고 있으므로 UI 스레드의 컨트롤에 접근하는 코드를 가진 DisplayFiles 메서드에 바로 접근하려고 하면 오류가 발생하는 것입니다.
    이럴때는 코드와 같이 Dispatcher를 이용하여 메서드를 실행하면 문제를 해결할 수 있습니다.

  5. 격리된 저장소에 저장된 파일의 리스트를 fileListBox 컨트롤에 출력하기 위한 DisplayFiles 메서드는 아래와 같이 구현합니다.

    private void DisplayFiles()
    {
        // 격리된 저장소에 저장된 전체 파일들의 이름을 ListBox에 출력한다.
        IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication();

        fileListBox.Items.Clear();

        string[] files = appStorage.GetFileNames();

        foreach (string filename in files)
        {
            fileListBox.Items.Add(filename);
        }
    }

    여기서도 저장소에 접근하기 위해서 IsolateStorageFile 을 이용하고 있으며, 파일 리스트를 얻어오기 위하여 GetFileNames 메서드를 활용하였습니다.

  6. fileListBox에 출력된 파일을 선택하였을때 contentTextBlock 에 내용을 출력하기 위하여 fileListBox의 SelectionChanged 이벤트 핸들러를 등록하고, 아래와 같은 코드를 추가하여 주십시오.

    private void fileListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (fileListBox.SelectedItem != null)
        {
            // 선택된 파일을 읽어서 출력한다.
            string selectedFilename = fileListBox.SelectedItem.ToString();

            if (System.IO.Path.GetExtension(selectedFilename) == ".txt")
            {
                IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication();

                using (StreamReader reader = new StreamReader(
                        new IsolatedStorageFileStream(selectedFilename, FileMode.Open, appStorage)))
                {
                    string content = reader.ReadToEnd();

                    contentTextBlock.Text = content;
                }
            }
        }
    }


  7. 실행결과는 아래와 같습니다.


부족한 부분은 샘플 프로젝트 파일을 첨부하니 참고하십시오.



격리된 저장소에 저장된 파일이나 디렉터리에 대한 정보를 가져오기 위한 FileInfo 클래스나 DirectoryInfo 클래스를 가져올 수 있는 방법이 없었습니다.
WP7 에서는 어플리케이션의 격리된 저장소와 공용 저장소 외에는 따로 접근할수가 없는듯 한데, 파일이나 디렉터리에 대한 정보를 가져올 수 없다는 점은 상당히 아쉬운 부분입니다.

끝까지 읽어주셔서 감사합니다.

즐거운 하루되십시오.

저작자 표시 동일 조건 변경 허락
Posted by WHiSTLE

트랙백 주소 http://blog.ntils.com/trackback/53 관련글 쓰기

댓글을 달아 주세요

  1. 2010/09/29 15:10

    좋은글 잘 읽었습니다.
    http://msdn.microsoft.com/ko-kr/library/system.io.isolatedstorage.isolatedstoragefile(VS.95).aspx
    여기에 디렉터리 등의 작업에 대한 샘플이 있네요.

  2. 2011/04/15 15:01

    비밀댓글입니다

이전버튼 1 이전버튼