HTML DOM 접근을 통한 간단한 방법이죠.
HTML DOM에 접근해야 하기 때문에 실버라이트를 로드할 때 <param name="enableHtmlAccess" value="true" />를 반드시 넣어줘야 해요.

  • referrer얻기
    HtmlPage.Document.GetProperty("referrer").ToString()
  • domain얻기
    HtmlPage.Document.GetProperty("domain").ToString()
  • 현재 URL얻기
    HtmlPage.Document.GetProperty("URL").ToString()

실버라이트 2에서는 HTML DOM에 대한 지원이 강화되었지만 위와 같이 직접적인 프로퍼티로 노출되어 있지 않는 것들은 GetProperty 메서드를 사용하여 얻을 수 있다는 것을 참고^^

또한 referrer를 제외한 domain과 현재 URL은 굳이 HTML DOM을 사용하지 않고 Application.Current.Host.Source를 통해 URI 정보를 얻을 수도 있으니 이쪽을 사용 할 것을 권해요.

신고
Posted by gongdo
키보드 입력과 마우스 휠 이벤트 지원

현재 실버라이트는 아직은 베타 상태이기도 해서 갖가지 버그도 있고 언어적 지원이 부실한 곳도 있어요. 하지만 언제나 그렇듯 아키텍처 자체에서 불가능한 일이 아니라면 어느 정도 핵hack과 트릭trick이 있게 마련이죠.

사용자 입력 인터페이스의 관점에서 제일 아쉬웠던 건 마우스 이벤트를 이동과 왼쪽 버튼에 한정시킨데다가 플래쉬 최대의 문제점인 오른쪽 버튼 클릭시 강제로 플래쉬 메뉴가 뜨는 것과 같이 덜렁 Silverlight 링크 메뉴 하나 던져준다는 점이었죠.

그 외에도 마우스 휠 이벤트가 아예 고려되어 있지 않고 키보드 입력 지원도 몇몇 키의 지원이 버그로 지원되지 않는 점이 있어요. 특히 방향키의 Down 이벤트가 발생하지 않는 버그는 좀 황당하네요.

이런 기본적인 문제점 들은 아마도 정식 버전이 나오면서 한방에 해결될 가능성이 높지만, 지금 당장 써먹을 필요가 있다면 핵을 쓰는 수밖에요.

Scriptable 지원

이런 문제점들의 해결은 의외로 간단한 편이에요. 실버라잇 컨트롤은 HTML 페이지에 호스팅 되며 사실 실버라잇에서 지원해주는 사용자 입력 이벤트는 HTML 페이지에서 발생되는 이벤트를 핸들링하여 전달해주는 것 뿐이죠.

따라서 HTML 페이지에서 실버라잇 컨트롤에 발생하는 이벤트를 Javascript로 핸들링하여 다시 실버라잇 컨트롤로 던져주는 코드를 작성한다면? 충분히 가능해 보이죠?

그런데 이것을 구현하려면 Javascript와 매니지드 코드와의 연동이 필요해요. 하지만 어떻게?

바로 이러한 종류의 연동을 위해 실버라잇은 Scriptable이라는 방법을 제공하고 있어요.

원래 이 주제는 QuickStarts의 일부로 다룰 예정이었는데 당분간 QuickStarts는 쉬고 있어서 아직 포스팅을 못했어요. 이 부분을 공부해보실 분은 http://silverlight.net/QuickStarts/Dom/ManagedCodeAccess.aspx 이곳을 참고하세요.

간략하게 소개하자면, 코드-비하인드에서 [Scriptable]이란 어트리뷰트를 부여한 public 클래스와 메소드 및 프로퍼티를 애플리케이션 전역의 Scriptable 개체로 등록하고 이것을 Javascript에서 접근할 수 있게 하는 방법이에요. 실버라잇 컨트롤에서 키를 눌렀을 때(KeyDown) 처리를 수행하는 간단한 샘플 코드를 작성해보자면 다음과 같아요.

Scriptable 샘플(KeyDown 이벤트 처리)
1. 코드-비하인드에 [Scriptable] 개체를 작성합니다. (기본적인 Using 구문 생략)
C#
using System.Windows.Browser; // 호스트 브라우저 클래스 지원

[Scriptable]
public class ScriptableSample
{
    [Scriptable]
    public void FireKeyDown(int keyCode)
    {
        // Down된 keyCode를 처리...
    }
}

2. 코드-비하인드에서 작성된 Scriptable 개체의 인스턴스를 생성하고 애플리케이션에 등록합니다. 이 작업은 일반적으로 페이지의 메인 코드에서 수행됩니다.
C#
using System.Windows.Browser; // 호스트 브라우저 클래스 지원

public class Page : Canvas
{
    public void Page_Loaded(object o, EventArgs e)
    {
        // Required to initialize variables
        InitializeComponent();

        ScriptableSample scriptable = new ScriptableSample();    // 생성
        WebApplication.Current.RegisterScriptableObject("ScriptableKey", scriptable);    // 등록
    }
}

3. Javascript로 Scriptable로 등록된 실버라잇 컨트롤의 개체를 획득하고 실버라잇 컨트롤에서 발생하는 이벤트를 핸들링하는 코드를 작성.
Javascript
var silverlightControl = null;
var scriptableObject = null;

// 실버라잇 컨트롤과 Scriptable 개체를 획득
// elementName - 실버라잇 컨트롤을 호스팅하는 엘리먼트의 이름
function LoadScriptable(elementName)
{
    // 실버라잇 컨트롤 바인딩
    silverlightControl = document.getElementById(elementName);

    // KeyDown 이벤트 핸들러 등록
    silverlightControl.onkeydown = HandleKeyDown;

    // 실버라잇 Scriptable 개체 지정, Key는 반드시 매니지드 코드에서 설정한 값을 사용할 것.
    // (setTimeout으로 딜레이를 줘야함)
    var scriptableKey = 'ScriptableKey';
    var scriptableObjectBinding = 'scriptableObject = silverlightControl.Content.' + scriptableKey;
    setTimeout('eval(scriptableObjectBinding);', 1);
}

// KeyDown 이벤트 핸들러
function HandleKeyDown(event)
{
    if (!event) // IE
        event = window.event;
   
    var
keyNum = 0;
    if(window.event) // IE
        keyNum = event.keyCode;
    else if(event.which) // Netscape/Firefox/Opera
        keyNum = event.which;

    if ( scriptableObject != null )
        scriptableObject.FireKeyDown(keyNum);
}

// Scriptable 개체를 로드
LoadScriptable('SilverlightControl');


여하튼, Scriptable을 사용하려면 최소한 HTML 페이지의 이벤트를 핸들링 할 추가적인 Javascript 코드, Scriptable 개체 및 Scriptable 메소드(또는 프로퍼티)를 선언하고 이것을 WebApplication에 등록해야 하는 상당히 귀찮은 작업을 반복해야 하죠.

게다가 Scriptable이 필요한 HTML 페이지 마다 저런 Javascript 코드를 Copy&Paste 한다는 건 너무 너무 너무 너무 너무나도 소모적이고 비효율적인 작업이 될 거에요.

단지 깔끔한 매니지드 코드로 작성된 클래스 하나로 이런 기능들을 한방에 지원할 수 없을까... 고민해보고 답을 얻었습니다.

HTML DOM에 동적으로 Javascript 추가

QuickStarts, HTML DOM의 사용에서 매니지드 코드에서 HTML DOM에 접근하고 제어하는 방법에 대해 소개했었죠. HTML DOM은 W3C에서 표준으로 제정되었기 때문에 어떤 언어라도 같은 기능을 가지고 있음을 확신할 수 있는데요, HTML DOM의 메소드 중에는 DOM에 태그 이름을 통해 새 엘리먼트를 동적으로 추가할 수 있는 AppendChild라는게 있어요.

그럼, Javascript 구문을 <script>태그로 추가하고 이 태그의 Text 내용을 위의 샘플에서 귀찮게 작성한 코드로 설정한다면? 앉아서, 아니 누워서 마음대로 Javascript를 추가하고 제어할 수 있겠죠?

동적으로 Javascript를 추가하는 아주 간단한 예를 들어 보자면...
C#
using System.Windows.Browser; // 호스트 브라우저 클래스 지원

public class Page : Canvas
{
    public void Page_Loaded(object o, EventArgs e)
    {
        // Required to initialize variables
        InitializeComponent();

        // HTML DOM에 Element 추가하기
        HtmlDocument doc = HtmlPage.Document;
        HtmlElement ele = doc.CreateElement("script");
        ele.SetProperty("Text", @"alert('매니지드 코드에서 작성한 스크립트!');");
        doc.DocumentElement.AppendChild(ele);
    }
}

간단하죠?

이 트릭을 이용하면 대부분의 Javascript를 매니지드 코드에서 동적으로 생성하여 붙여 넣을 수 있는 엄청난 가능성이 생겨요.

이번 포스팅에서는 샘플들의 프로젝트를 첨부하지 않았고 대신, 실제로 써먹을 만한 완성된 프로젝트를 첨부했으니 코드를 보고 연구해보시길 바래요. Scriptable은 약간은 복잡하고 어려운 부분이니까 강좌 형식으로 쓰고 싶었는데 지금은 그렇게 하기가 무리가 있네요. :(

프로젝트는 키보드 입력(KeyDown, KeyPress, KeyUp) 및 마우스 휠 이벤트 정보를 출력하는 테스트 페이지에요.

프로젝트 테스트 :
http://gongdo.oranc.co.kr/Silverlight/Samples/ScriptableTester/TestPage.html

ScriptableTester.zip

스크립터블 프로젝트



신고
Posted by gongdo
Silverlight 1.1 바로 시작하기

HTML DOM의 사용
준비 사항
Silverlight 개발의 기초에서 개발에 필요한 도구와 기술에 대해 설명하고 있습니다.
이벤트 핸들링에서 개체의 이벤트를 처리하는 방법에 대해 설명하고 있습니다.

HTML DOM(Document Object Model)과 JavaScript에 대한 사전 지식이 필요합니다. http://www.cadvance.org/ 에 HTML부터 XML까지 아주 잘 정리되어 있으니 참고하시기 바랍니다.

예제 중간에 정규표현식이 사용되며 여기에 대한 자세한 설명은 http://msdn2.microsoft.com/ko-kr/library/hs600312(VS.80).aspx 을 참고하십시오.

이 글에서 사용된 예제는 http://gongdo.oranc.co.kr/Silverlight/Samples/HtmlDom/TestPage.html 에서 미리 테스트해볼 수 있습니다.

HTML DOM 참조

호스트 HTML의 DOM에 접근하는 방법
Silverlight는 System.Windows.Browser 네임스페이스를 통해 호스트 HTML의 DOM에 접근하고 핸들링 할 수 있는 클래스들을 제공합니다.


매니지드 코드에서 HTML DOM에 접근하는 방법을 알아보기 전에 사용자와 상호작용이 가능한 간단한 HTML 페이지를 만들어 보겠습니다. 또한 이 예제는 Silverlight내의 매니지드 코드로 HTML DOM에 접근하는 방법을 보여주기 위한 것이므로 XAML 디자인도 간소화 하였습니다.

Howto:5-1 사용자와 상호작용하는 HTML 페이지 샘플 및 XAML 페이지 디자인
1. HtmlDom이라는 이름으로 새 Silverlight 프로젝트를 생성합니다.

2. TestPage.html의 BODY태그 안에 다음 DIV 코드 블럭을 추가합니다.
HTML
<div id="userInteraction">
    <p style="font-size:20px; font-weight:bold;">매니지드 코드에서 HTML DOM 접근 예제</p>
    <span style="font-size:14px">1. HTML DOM의 이벤트 핸들링</span><br />
    예제 1) e-mail 유효성 검사 :
    <input type="text" id="email" size="50px" /><input type="button" id="emailSubmit" value="체크" /><br />
    <span style="margin-left:50px">검사 결과 : </span>
    <input type="text" id="emailValidationResult" size="70" /><br /><br />

    <span style="font-size:14px">2. 호스트 HTML 페이지의 속성 얻기</span><br />
    예제 2) 페이지 속성 정보 : 
    <input type="button" id="propertiesSubmit" value="속성 가져오기" /><br />
    <textarea id="properties" cols="70" rows="7"></textarea><br /><br />

    <span style="font-size:14px">3. HTML DOM의 메소드 호출</span><br />
    예제 3) URL 유효성 검사 및 이동 : 
    <input type="text" id="url" size="30" /><input type="button" id="urlSubmit" value="체크 & 이동" /><br /><br />
</div>

예제는 1. 간단한 이메일 유효성 검사, 2. HTML 페이지의 속성 얻기, 3. HTML DOM의 메소드 호출하는 방법 입니다.

3. Page.xaml의 루트 엘리먼트인 Canvas를 300 x 50 픽셀로 설정하고 TextBlock 하나를 선언합니다. 다른 HTML 엘리먼트와 구분하기 위해 배경 색상을 따로 설정하였습니다.
XAML
<Canvas x:Name="parentCanvas"
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Loaded="Page_Loaded"
    x:Class="HtmlDom.Page;assembly=ClientBin/HtmlDom.dll"
    Width="300" Height="50" Background="#FFFF9966">
    <TextBlock Text="Access HTML DOM from managed code." />
</Canvas>

4. TestPage.html.js에서 호스팅할 Silverlight 컨트롤의 크기도 300 x 50으로 설정합니다.
Script
//...전략...
    properties: {
        width: "300",
        height: "50",
//...후략...

매니지드 코드에서 HTML DOM에 접근하기 위해서 다음과 같은 코드를 사용합니다.

C#
using System.Windows.Browser;
HtmlDocument document = HtmlPage.Document;

앞서 얘기했듯이 System.Windows.Browser 네임스페이스는 자신을 호스팅하는 HTML 페이지에 접근하기 위한 다양한 클래스와 메소드를 제공합니다. 호스트 HTML 페이지는 HtmlPage 클래스로 캡슐화되고 HtmlDocument 클래스는 HTML DOM 그 자체를 캡슐화 하며 HtmlPage의 Document 프로퍼티를 통해 호스트 HTML의 HTML DOM에 대한 참조를 얻을 수 있습니다.

HTML DOM 엘리먼트의 이벤트 핸들링
HtmlPage.Document 프로퍼트를 통해 얻은 HTML DOM에 대한 참조를 사용하여 HTML DOM 엘리먼트에 대한 이벤트 핸들러를 매니지드 코드로 작성한 함수로 지정할 수 있습니다. 여기에서는 앞에서 작성한 HTML 페이지의 1번 예제를 처리하는 방법을 제시합니다.
Howto:5-2 HTML DOM 엘리먼트의 이벤트 핸들링
1. Page.xaml.cs에 HTML 페이지 정보 처리와 정규 표현식 처리를 위한 네임스페이스를 추가하고 HTML DOM에 대한 참조를 클래스의 지역 변수에 저장합니다.
C#
// 다음 네임스페이스를 추가
using
System.Windows.Browser;

using System.Text.RegularExpressions;

// 프로젝트의 네임스페이스 선언은 생략
public partial class Page : Canvas
{
    HtmlDocument _document; // HTML DOM 참조
    public void Page_Loaded(object o, EventArgs e)
    {
        // Required to initialize variables
        InitializeComponent();
        _document = HtmlPage.Document;
    }
}

2. 예제 1의 버튼 클릭 이벤트를 받아서 처리할 이벤트 핸들러를 선언합니다. 내부 코드는 아래에서 추가하도록 하고 여기에서는 단지 이름만을 선언해 놓습니다.
C#
private void OnEmailClicked(object sender, HtmlEventArgs e)
{
}

3. 클래스의 생성자에 HTML DOM 참조로부터 실제로 이벤트를 부여할 엘리먼트의 참조를 얻고 이벤트 핸들러를 지정하는 코드를 추가합니다. 과정 2에서 미리 이벤트 핸들러를 선언했기 때문에 코드 입력 과정에서 인텔리센스의 도움을 받을 수 있습니다.
C#
// 예제1의 버튼 ID를 통해 이벤트 핸들러 지정
HtmlElement btnEmail = _document.GetElementByID("emailSubmit");
btnEmail.AttachEvent("onclick", new EventHandler<HtmlEventArgs>(this.OnEmailClicked));

4. 이제 실제 작동할 이벤트 핸들러 코드를 작성합니다.
C#
private void OnEmailClicked(object sender, HtmlEventArgs e)
{
    // 엘리먼트의 참조 얻기

    HtmlElement txtEmail = _document.GetElementByID("email");
    HtmlElement txtResult = _document.GetElementByID("emailValidationResult");

    // 엘리먼트의 값을 얻어와 정규 표현식으로 점검하고 결과 설정
    string email = txtEmail.GetAttribute("value");
    if (email == null)    // 값이 설정되지 않은 경우 null이 올 수 있으므로 주의
        email = "";
    Regex reg = new Regex(@"\w+[@]\w+\.\w+");
    if (reg.Match(email).Success)
    {
        txtResult.SetAttribute("value", " E-mail [" + email + "] .");
    }
    else
    {
        txtResult.SetAttribute("value", " E-mail [" + email + "] .");
    }
}

5. 프로젝트를 빌드하고 F5를 눌러 테스트 해봅니다.


호스트 HTML 페이지의 속성(Property) 참조
HtmlPage 클래스는 또한 현재 호스트 HTML 페이지의 유용한 속성을 제공합니다. 예제 2를 작성해 보겠습니다.
Howto:5-3 호스트 HTML 페이지의 속성 참조
1. 예제 2의 버튼 클릭 이벤트를 받아서 처리할 이벤트 핸들러를 선언합니다. 내부 코드는 아래에서 추가하도록 하고 여기에서는 단지 이름만을 선언해 놓습니다.
C#
private void OnPropertiesClicked(object sender, HtmlEventArgs e)
{
}

2. 클래스의 생성자에 HTML DOM 참조로부터 실제로 이벤트를 부여할 엘리먼트의 참조를 얻고 이벤트 핸들러를 지정하는 코드를 추가합니다.
C#
// 예제2의 버튼 ID를 통해 이벤트 핸들러 지정
HtmlElement btnProperties = _document.GetElementByID("propertiesSubmit");
btnProperties.AttachEvent("onclick", new EventHandler<HtmlEventArgs>(this.OnPropertiesClicked));

3. 이제 실제 작동할 이벤트 핸들러 코드를 작성합니다.
C#
private void OnPropertiesClicked(object sender, HtmlEventArgs e)
{
    // 엘리먼트의 참조 얻기

    HtmlElement txtProperties = _document.GetElementByID("properties");

    //
호스트 HTML 페이지의 속성들을 표시
    string
output = "";
    output = "절대 경로 : " + HtmlPage.DocumentUri.AbsolutePath;
    output += "\n플랫폼 : " + HtmlPage.BrowserInformation.Platform;
    output += "\n에이전트 : " + HtmlPage.BrowserInformation.UserAgent;
    txtProperties.SetAttribute("value", output);
}

4. 프로젝트를 빌드하고 F5를 눌러 테스트 해봅니다.

호스트 HTML DOM의 메소드 호출
HtmlPage 클래스는 또한 HTML DOM과 관계된 메소드를 제공합니다. 예제 3에서 지정된 URL로 이동하는 Navigate 메소드를 호출하는 예제를 작성해 보겠습니다.
Howto:5-4 호스트 HTML DOM의 메소드 호출
1. 예제 3의 버튼 클릭 이벤트를 받아서 처리할 이벤트 핸들러를 선언합니다. 내부 코드는 아래에서 추가하도록 하고 여기에서는 단지 이름만을 선언해 놓습니다.
C#
private void OnNavigateClicked(object sender, HtmlEventArgs e)
{
}

2. 클래스의 생성자에 HTML DOM 참조로부터 실제로 이벤트를 부여할 엘리먼트의 참조를 얻고 이벤트 핸들러를 지정하는 코드를 추가합니다.
C#
// 예제3의 버튼 ID를 통해 이벤트 핸들러 지정
HtmlElement btnNavigate = _document.GetElementByID("urlSubmit");
btnNavigate.AttachEvent("onclick", new EventHandler<HtmlEventArgs>(this.OnNavigateClicked));

3. 이제 실제 작동할 이벤트 핸들러 코드를 작성합니다.
C#
private void OnNavigateClicked(object sender, HtmlEventArgs e)
{
    // 엘리먼트의 참조 얻기
    HtmlElement txtUrl = _document.GetElementByID("url");

    // 엘리먼트의 값을 가져와 정규 표현식으로 점검하고 결과 설정
    string url = txtUrl.GetAttribute("value");
    if (url == null)
    url = "";

    // 먼저 스키마 지정(http://등등)이 없으면 http://를 추가
    Regex regPre = new Regex(@"([\w]+:)?//\w*");
    if (!regPre.Match(url).Success)
        url = "http://" + url;

    // 다시 검사(RFC URL규정)
    Regex reg = new Regex(@"(([\w]+:)?//)?(([\d\w]|%[a-fA-f\d]{2,2})+(:([\d\w]|%[a-fA-f\d]{2,2})+)?@)?([\d\w][-\d\w]{0,253}[\d\w]\.)+[\w]{2,4}(:[\d]+)?(/([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)*(\?(&?([-+_~.\d\w]|%[a-fA-f\d]{2,2})=?)*)?(#([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)?");
    if (reg.Match(url).Success)
    {
        HtmlPage.Navigate(url);
    }
    else
    {
        txtUrl.SetAttribute("value", " URL.");
    }
}

4. 프로젝트를 빌드하고 F5를 눌러 테스트 해봅니다.

예제 프로젝트를 첨부하였습니다.



디버깅 팁

예제와 같이 어떤 개체의 참조를 문자열로부터 얻는 코드는 대단히 위험합니다. 컴파일러는 코드를 빌드하는 시점에서는 그 문자열에 사소한 오타나 문제점이 있어도 그것을 감지할 수 없기 때문에 런타임에 예외를 발생시킬 가능성이 매우 높으며 따라서 반드시 try~catch 구문을 사용하여 문제가 발생할 경우 예외 처리를 수행해야 합니다.

예를 들어 초기에 HTML 페이지에 'test'라는 ID의 엘리먼트를 배치하고 페이지를 작성하였고 매니지드 코드에서 이 엘리먼트에 디버깅 결과를 출력하였다고 합시다. 시간이 흘러 더 이상 test가 필요 없게 되었고 페이지에서 이 엘리먼트를 삭제했지만 깜빡하고 매니지드 코드에서는 관련 코드를 제거하지 않았다면 해당 코드가 실행될 때 더 이상 test라는 ID를 가진 엘리먼트를 찾을 수 없으므로 Silverlight 애플리케이션은 예외를 발생시키고 그 이후의 어떤 코드도 실행되지 않게 될 것입니다.

항상 런타임에 동적으로 바인딩 되는 개체를 참조할 때에는 반드시 예외 처리를 할 것을 강력하게 권장합니다.

참고

Silverlight 공식 QuickStarts 참고:
http://silverlight.net/QuickStarts/Dom/DomAccess.aspx

.NET Framework의 정규표현식 참고:
http://msdn2.microsoft.com/ko-kr/library/hs600312(VS.80).aspx

정규표현식 라이브러리:
http://regexlib.com/
신고
Posted by gongdo


티스토리 툴바