메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

XAML로 사진 업로더 만들기

한빛미디어

|

2006-11-02

|

by HANBIT

12,449

제공: 한빛 네트워크
저자: Jack Herrington, 이대엽 역
원문: Building Photo Uploaders with XAML

소개

만약 여러분이 저와 같은 사용자 경험 고물상이라면 XAML은 인터페이스 골동품과도 같습니다. 여러분이 아직 윈도 비스타의 XAML 데모를 본적이 없거나, 혹은 자유로이 구할 수 있는 WinFX SDK를 사용했던지 간에, 만약 그렇다면 여러분은 세상 헛산겁니다. XAML은 태그기반 언어로서 기절초풍할 인터페이스를 빠르고 쉽게 작성하는데 사용합니다.

이 기사에서는 이러한 애플리케이션들 중의 하나를 개발하는 방법 뿐만 아니라 그것을 활용하는 방법도 보여줄 것입니다. 이를 위한 예제로 저는 이미지 파일을 간단한 PHP 웹 애플리케이션에 올리는 사진 업로더를 작성할 것입니다.

PHP 웹 애플리케이션 만들기

여정의 첫 번째 정류장은 사진을 받아들여 업로드된 이미지가 무엇인지를 보여주는 PHP 웹 애플리케이션을 작성하는 것입니다. PHP 애플리케이션은 이 기사의 주요 초점이 아니기 때문에 저는 그 애플리케이션을 상당히 단순하게 만들 것이며, 그리고 파일을 저장하고 검색하는데 파일에 기반한 솔루션만을 사용할 것입니다. 확실히 실무에서 이 시스템을 사용하려면 여러분은 좀 더 강건한 이미지 저장 및 검색 시스템을 가지길 원할 것입니다.

우선 저는 업로드된 파일을 받아 media라 이름지어진 하위 디렉터리로 이동시키는 페이지를 작성할 것입니다. 이 PHP 스크립트가 표 1에 나와 있습니다.

리스트 1. upload.php

이야~, 이것보다 더 간단할 순 없겠네요. 여기서 저는 업로드된 파일을 받아들여 그것들을 media 디렉터리로 이동시키기 위해 PHP의 move_uploaded_file 함수를 사용하였습니다. 확실히 이상적인 애플리케이션은 충돌하는 파일명을 확인해야 되겠지만 이것은 단순히 테스트용 애플리케이션이라 단순하게 만들겠습니다.

업로드된 것들을 보기 위해 저는 media 디렉터리의 내용을 HTML 페이지로 보여줄 또 다른 스크립트가 필요합니다. 그러한 PHP 스크립트가 표 2에 나와 있습니다.

리스트 2. index.php


이것은 꽤 단순한 스크립트입니다. 이 스크립트는 페이지를 만드는 태그를 만든 다음 테이블을 만드는 태그를 만드는 걸로 시작합니다. 그 다음 디렉터리를 열고 각각의 파일에 대한 행과 각 행 내에 두 개의 셀을 생성하는데, 하나는 파일의 이름을 가지며 또 다른 하나는 이미지를 보여줄 이미지 태그를 가지는 태그를 테이블에 생성합니다.

일단 제가 XAML 업로더 애플리케이션을 작성하여 몇 가지 테스트 이미지를 업로드 해보았으므로 이 페이지의 결과물을 보여드리도록 하겠습니다.

XAML 시작하기

다음 단계는 XAML 업로더 애플리케이션을 작성하는 것입니다. 그리고 이것은 WinFX SDKVisual Studio 2005를 설치하는 것으로부터 시작합니다. 이 둘 모두 윈도 XP나 여러분이 구동시킬 만한 용기를 가지고 있다면 윈도 비스타 베타에서 모두 설치 가능합니다. SDK는 무료이며 비주얼 스튜디오 2005의 평가버전 역시 구할 수 있습니다. SDK가 몇 기가바이트 되므로 이것들을 다운로드 하는 동안의 긴 시간을 어떻게 보낼지 계획해 두시는 게 좋을 겁니다.

개발도구의 설치가 완료되고 나면 인터페이스에 대한 작업을 시작할 수 있습니다.

인터페이스 기초

전 마음속으로 업로더의 간단한 외관을 그려보았습니다. 상단에는 이미지 썸네일의 행이 있고, 가운데에는 선택된 썸네일의 미리보기가 있습니다. 썸네일이 선택되면 업로드 버튼이 나타날 것이며, 그 버튼을 누르면 파일을 서버로 전송할 것입니다.

가장 먼저 해야 할 일은 새로운 XAML 애플리케이션 프로젝트를 생성하고 프로젝트의 이름을 UploaderProject라 명명합니다. 프로젝트 안에 저는 Uploader라 불리는 새 XAML 페이지를 생성합니다. 이렇게 하면 Uploader.xaml과 Uploader.xaml.cs의 두 개의 파일이 생성됩니다. XAML 파일은 객체와 레이아웃을 정의하며 CS 파일은 페이지에 대한 코드를 정의합니다. 그것은 페이지가 로드되거나 버튼이 클릭되는 등의 일이 발생할 때 호출되는 C# 클래스입니다. 비주얼 스튜디오를 이용하여 저는 레이아웃을 시각적으로 집어 넣을 수 있었습니다. 하지만 저는 코드를 보면서 작업하는 걸 좋아하며 페이지의 최초버전은 표 3에 나타나 있습니다.

리스트 3. Uploader.xaml


  
    
  
  
  
    
    
    
  
  
  
    
  

  

  

  
  

저는 이 페이지를 Grid 클래스에 기반하여 작성하였습니다. Grid 클래스는 수많은 XAML 레이아웃 관리자중 하나입니다. 그것은 HTML의 테이블(table)과 유사합니다. 이 경우 저는 3개의 행을 정의할 것입니다. 첫 번째 것은 썸네일이 위치할 곳입니다. 가장 큰 두 번째 행은 이미지 미리보기가 위치할 곳입니다. 그리고 세 번째 행은 자그마한 상태 표시줄을 가질 것입니다. Grid.RowDefinitions 태그를 살펴보시면 여러분은 이러한 3개의 행들과 그것의 상대 너비들이 배치되어 있는 것을 보실 수 있습니다.

그 다음으로는 비주얼 객체들을 정의합니다. 상단에는 썸네일을 가지고 있기 위한 ScrollViewer내의 StackPanel를 생성합니다. StackPanel은 Grid와 같은 또 다른 조직화 클래스이나 이 경우에는 단순히 포함된 객체들을 수평 혹은 수직적으로 쌓아 올리는 역할만을 수행합니다. ScrollViewer는 StackPanel 주위에는 스크롤바를 위치시킵니다.

이미지 객체는 선택된 썸네일에 대한 미리보기입니다. 그 아래에는 업로드(Upload) 버튼이 미리보기와 동일한 Grid의 셀에 위치하지만 시각적으로 분간할 수 있게끔 우측 하단으로 정렬됩니다.

버튼 셀이 위치한 곳에는 상태표시줄을 포함하는 TextBox가 위치합니다.

여기에서 재미있는 점은 HTML에서 이루어지는 것과는 다소 다른 접근법이 이 레이아웃 모델에서 채택되었음을 눈치채는 것입니다. HTML의 이라면 3개의 행이 있어야 하며, 각각의 셀 하나하나마다 각 셀의 내용들이 그 셀의
를 이용하여 정의될 것입니다. XAML에서는 컨테이너를 정의한 다음 특수한 속성, 이 경우에는 컨테이너 내의 요소가 필요한 곳을 지정하기 위해 Grid.Row와 Grid.Column을 사용합니다.

여기에 양념을 곁들이기 위해 마우스를 위에다 올려 놓으면 이미지 썸네일 주위에 글로우(glow) 효과를 주는 코드를 파일의 상단에 추가할 것입니다. 이 코드가 표 4에 나타나 있습니다.

리스트 4. 마우스오버 글로우 정의
  
    
  
이것은 XAML 버전의 인라인 CSS 스타일시트입니다. 이 경우 저는 이미지 타입과 IsMouseOver 속성이 true로 설정될 때 걸리는 트리거를 적용시킨 스타일을 정의합니다. BitmapEffect 속성이 변경되면 바깥쪽 글로우 효과가 추가됩니다. 바깥쪽 글로우 이미지에는 앤티-알리아싱 글로우 시각효과를 추가합니다. 여러분은 글로우의 색상, 글로우의 크기, 그리고 그 밖의 것들을 지정할 수 있습니다.

XAML은 이미지 뿐만 아니라 거의 모든 객체에 적용시킬 수 있는 비트맵 효과들의 복주머니를 제공합니다. 여러분이 Adobe 포토샵과 그것의 이펙트 패키지에 익숙하다면 여러분은 XAML 툴킷에서 제공해주는 것과의 유사점을 발견하실 수 있을 겁니다. 그것들을 설명하기 위해 저는 표 5에서 보여지는 것과 같이 업로드 버튼에 배경을 선형 그라디언트 채움(linear gradient fill)을 가지도록 변경하는 자그마한 시각적인 표시효과를 줄 것입니다.

리스트 5. 업로드 버튼의 외관 바꾸기
  
제가 버튼의 배경 속성을 변경한 것에 주목해 주십시오. XAML의 모든 UI 요소들은 여러분이 개별 객체 수준에서든 요소의 클래스를 지정하든 표에 나타나 있는 방식으로 변경할 수 있는 XAML 요소들로 구성됩니다. 예를 들어 모든 버튼-혹은 특정 클래스의 모든 버튼-에 새로운 배경, 버튼의 이름에 사용되는 새로운 글꼴, 버튼의 내용을 위치시키기 위한 새로운 템플릿, 기타 등등의 것들이 주어질 수 있습니다. 버튼의 모든 상호작용성은 유지하면서 말입니다. 그러므로 예를 들어, 아이콘 버튼을 만드는 일은 XAML에서는 일도 아닙니다.

백엔드와 연동하기

이제 모든 인터페이스들을 한데 묶어놓으면 사용자 인터페이스 뒷단에 있는 Uploader.xaml.cs 클래스에 대한 작업을 시작할 수 있습니다. 가장 먼저 해야 할 일은 각각의 이미지에 관한 정보들을 가지고 있을 클래스를 작성하는 것입니다. 저는 그 클래스를 ImageInfo라고 부를 것이며 표 6에 나타나 있습니다.

리스트 6. ImageInfo private 클래스
using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Media.Imaging;
using System.Collections;
using System.Configuration;

namespace UploaderProject
{
  public class ImageInfo
  {
    private BitmapImage m_Image = null;
    public BitmapImage Image { get { return m_Image; } }

    private string m_sPath = null;
    public string Path { get { return m_sPath; } }

    private ImageInfo() {
      m_Image = null;
      m_sPath = "";
    }

    public ImageInfo( string sPath, BitmapImage image ) {
      m_sPath = sPath;
      m_Image = image;
    }
  }
이 클래스는 사실 전용 파일을 가질 만큼 중요하지는 않기 때문에 저는 그냥 Uploader 인터페이스 코드의 정의 파일과 동일한 위치에 박아놓을 겁니다.

백엔드의 작업을 완료하기 위해 저는 인터페이스 자체에 대한 코드를 표 7에 나타나 있는 것과 같이 Uploader 클래스로 작성합니다.

리스트 7. 인터페이스 이면의 Uploader 클래스
  public partial class Uploader
  {
    private Hashtable m_ImageLookup = new Hashtable();
    private WebClient m_Client = new WebClient();
    private ArrayList m_PendingFiles = new ArrayList();
    private ImageInfo m_CurrentlySelected = null;
    private string m_sCurrentDownload = "";

    public Uploader() {
      m_Client.DownloadFileCompleted += new AsyncCompletedEventHandler(
          WebClient_DownloadFileCompleted );
      m_Client.DownloadProgressChanged += new
        DownloadProgressChangedEventHandler(
          WebClient_DownloadProgressChanged );
    }

    void WebClient_DownloadProgressChanged(object sender,
        DownloadProgressChangedEventArgs e) {
      PercentDone.Text = String.Format("{0} - {1}% done",
        m_sCurrentDownload, e.ProgressPercentage);
    }

    void WebClient_DownloadFileCompleted(object sender,AsyncCompletedEventArgs e) {
      PercentDone.Text = "";
      StartUpload();
    }

    void StartUpload() {
      string sPath = (string)m_PendingFiles[0];
      m_sCurrentDownload = Path.GetFileName(sPath);
      m_PendingFiles.RemoveAt(0);

      string sUploadPage = ConfigurationSettings.AppSettings.Get("UploadPage");

      if ( m_Client.IsBusy == false )
        m_Client.UploadFileAsync(new Uri(sUploadPage), "POST", sPath, null);
    }

    protected override void OnInitialized(EventArgs e) {
      string[] sFiles = Directory.GetFiles( Directory.GetCurrentDirectory() );
      foreach( string sFile in sFiles ) AddImageButton( sFile );
    }

    void Upload_Click(object sender, RoutedEventArgs e) {
      if (m_CurrentlySelected != null)
      {
        m_PendingFiles.Add(m_CurrentlySelected.Path);
        StartUpload();
        Upload.Visibility = Visibility.Hidden;
        m_CurrentlySelected = null;
      }
    }

    protected void AddImageButton(string sPath) {
      if (Path.GetExtension(sPath).ToLower() != ".jpg") return;

      BitmapImage image = null;
      try {
        image = new BitmapImage();
        image.BeginInit();
        image.UriSource = new Uri("file://" + sPath);
        image.EndInit();
      } catch { }

      if (image != null)
      {
        int nFixedHeight = 100;

        double dScaleRatio = (double)image.Height / (double)(nFixedHeight - 10);

        Canvas b = new Canvas();
        b.Width = (int)(image.Width / dScaleRatio) + 5;
        b.Height = nFixedHeight;

        Image bi = new Image();
        bi.Source = image;
        bi.Width = (int)(image.Width / dScaleRatio);
        bi.Height = nFixedHeight - 10;
        bi.Style = (Style)DocumentRoot.FindResource("TopImage");
        bi.SetValue(Canvas.LeftProperty, 5.0);
        bi.SetValue(Canvas.TopProperty, 5.0);
        bi.MouseUp += new MouseButtonEventHandler(TopImage_MouseUp);
        b.Children.Add(bi);

        m_ImageLookup[bi] = new ImageInfo(sPath, image);

        ImageList.Children.Add(b);
      }
    }

    void TopImage_MouseUp(object sender, MouseButtonEventArgs e) {
      if (m_ImageLookup[sender] != null)
      {
        m_CurrentlySelected = (ImageInfo)m_ImageLookup[sender];
        DisplayImage.Source = m_CurrentlySelected.Image;
        Upload.Visibility = Visibility.Visible;
      }
    }
  }
}
코드의 대부분은 AddImageButton 메소드에 들어있는데, 이 메소드는 이미지 썸네일을 화면에 추가합니다. 메소드가 하는 일은 동적으로 새로운 Canvas 객체를 생성한 다음 이미지 객체가 위치한 곳에서 그 객체를 인터페이스의 ImageList 요소에 추가하는 것입니다. 동적 HTML과 같이 새 인터페이스 항목을 화면에 추가하는 것은 시각 트리에 새로운 노드를 추가하는 것 만큼이나 쉽습니다.

이 예제의 다른 흥미로운 부분은 Upload 버튼이 클릭되면 호출되는 Upload_Click 메소드입니다. 이 메소드는 WebClient 객체를 이용하여 웹 서버로의 파일 전송을 시작합니다. 이 WebClient 객체는 스크립팅 가능한 웹 브라우저의 대용물인데, 여러분은 페이지의 내용을 가져오고, 폼을 포스트하며, 그리고 심지어 쿠키와 세션을 저장하는 데 이 객체를 사용할 수 있습니다. 그러므로 여러분이 어떤 인증에 관한 요구사항을 가지고 있다면 여러분은 여전히 최초 로그인시에 WebClient를 사용할 수 있으며 그 다음에 파일을 업로드 시킬 수 있습니다.

WebClient를 이용하는 것의 가치는 Uploader에 어떠한 특별히 비정형화된 방법이 없다는 것입니다. 이 메커니즘은 웹 브라우저가 파일을 업로드 하는 것과 정확히 동일한 방법을 사용하므로 PHP 웹 애플리케이션이 이 메커니즘을 지원하도록 아무것도 변경할 필요가 없습니다

애플리케이션이 필요로 하는 한 가지는 이미지 업로드를 처리할 페이지의 URL 입니다. 이는 표 8에 나타나 있는 것처럼 애플리케이션에 대한 XML 환경설정 파일에 설정됩니다.

리스트 8. 환경설정 파일


  
    
  

제 경우에는 동일한 라우터상에 있는 제 윈도 박스 옆에 놓여져 있는 Mac OS X상에 PHP 애플리케이션이 작동되고 있으므로 주소를 직접 입력해 놓았습니다. 최종 산출물에서 여러분은 http://mycompany.com/upload.php과 같이 IP 주소에 대한 DNS를 통해 해석된 호스트명을 사용하게 될 것입니다.

시험해 보기

PHP 웹 사이트가 완료되고 업로더의 프론트엔드와 백엔드 코드가 작성되면 이제 애플리케이션을 구동시켜서 그것이 작동하는지 알아볼 차례입니다. 그래서 저는 F5를 눌러 디버거를 실행시킨 다음 그림 1-1과 같은 것을 보게 됩니다.

그림1-1
그림 1-1. 업로더의 초기화면

다음에 제가 아름다운 노란색 꽃을 선택하자 C# 코드는 그 이미지가 프레임의 가운데로 오도록 지정합니다. 이것이 그림 1-2에 나타나 있습니다.

그림1-2
그림 1-2. 이미지 선택 후의 화면

이 지점에서 재미있을 것 같은 것은 창의 크기를 리사이즈 한다는 것인데, 창은 늘어나는 반면 요소의 레이아웃은 그대로 유지된다는 것에 주목해 주십시오. 이는 XAML을 사용하는 이점들 중 부분에 불과하며 그리드 레이아웃 시스템의 경우 특히 그러합니다. 계속해서 저는 업로드 버튼을 누르고 업로드가 완료될 때까지 기다립니다. 그 다음, 저는 이 사이트의 index.php를 탐색하여 파일이 정말로 제 요청에 대해 업로드 되었는지 그림 1-3과 같이 살펴봅니다.

그림1-3
그림 1-3. 업로드된 이미지 보기

드디어 XAML과 WinFX SDK이 제공하는 매력적인 기능을 이용한 초보적인 수준의 이미지 업로더가 만들어졌습니다. 이는 또한 오픈소스 세계에 대한 윈도의 세계적인 활약을 보여주는 괜찮은 예시이기도 합니다.

결론

최근 윈도 비스타에 관한 많은 이야기들이 오고 가고 있습니다. 출시될 것인가? 언제 출시될 것인가? 무엇을 탑재할 것인가? 저는 비스타가 출시될 것이라고 생각하고 있으며 WPF(Windows Presentation Foundation, XAML을 포함하고 있음)에 대한 저의 경험상 윈도가 무엇보다 먼저 WPF를 탑재할 것이라 확신하고 있습니다. 왜냐하면 그것은 비스타의 핵심이기 때문이죠. 하지만 부차적으로는 제가 참여하고 있는 프로젝트를 보자면 그것은 견고하며 라이브러리 설계가 상당히 괜찮다고 생각됩니다.

그런데 한가지 주의사항을 알려드릴 텐데요, 그것은 기존의 참조물들을 조심하라는 것입니다. WinFX SDK는 지금껏 몇 가지 변화가 있었습니다. 클래스명이 바뀌었으며, 속성이 변경되었으며, 그리고 파일의 기록방식이 변경되었습니다. 여러분은 특정 태그나 클래스의 사용법에 대한 사항들에 대한 정보를 얻으려면 구글에서 검색하시면 보실 수 있으실 겁니다. 솔직히 말해서 마이크로소프트 웹 사이트에도 이전 버전의 SDK의 낡은 정보들이 있습니다. 그러므로 여러분은 이것을 이용해서 개발할 때나 복사해서 붙여넣기할 때 코드가 현재 버전의 API에서도 여전히 작동하는지를 주의깊게 살펴보아야 합니다.

거듭 말씀 드리지만 저는 WPF에 감명을 받았으며 엔지니어와 그래픽 디자이너가 WPF를 이용하여 그들의 인터페이스를 다음 수준으로 이끌어가는데 있어 상당한 잠재력이 있다고 생각합니다.

Jack Herrington는 20년의 경험을 가진 수석 소프트웨어 엔지니어입니다. 그는 Code Generation in Action, Podcasting Hacks, PHP Hacks의 3권의 책을 펴낸 저자이며 30개 이상의 다양한 분야의 기술을 다룬 기사를 쓰기도 하였습니다.
TAG :
댓글 입력

최근 본 책0