안녕하세요? 맨날맑음입니다.

닷넷의 리플렉션(Reflection)을 이용하면 어셈블리의 메터데이터 정보를 알아올 수 있고, 이를 이용해 지연바인딩(Lazy Binding)이나 동적으로 코드를 생성할 수 있습니다. 이번 포스팅에서는 리플렉션을 이용한 동적 코드생성에 대해 알아 보도록 하겠습니다.

 

간단한 예제를 통해 동적 코드생성에 대해 알아 볼 텐데요. 예제는 어셈블리를 동적으로 생성/실행하는 콘솔어플리케이션 프로젝트(App)와, 동적으로 코드를 생성하는 클래스 라이브러리 프로젝트(CodeGenerator)로 구성 됩니다.

CodeGenerator 클래스에서는 동적으로 DynamicAssembly를 생성하고, DynamicClass, DynamicMethod를 만들어 그 안에 "Dynamic Method Call"이라는 메시지를 프린트하는 코드를 작성하게 만들도록 하겠습니다.

 

  1. 파일 -> 새로 만들기 -> 프로젝트를 선택하여 빈 솔루션을 생성합니다.
  2. 솔루션 탐색기의 솔루션 탭을 마우스 오른쪽 클릭하여, 콘솔어플리케이션 프로젝트(App)와 클래스라이브러리 프로젝트(CodeGenerator)를 생성합니다.
  3. Programe.cs 와 Class1.cs의 이름을 각각 App와 CodeGenerator로 변경합니다.

#1. CodeGenerator 클래스 작성

CodeGenerator.cs에 다음의 코드를 입력합니다.

public class CodeGenerator
{
    public Type DynamicType { getprivate set; }
    public CodeGenerator()
    {
        //현재 어플리케이션 도매인을 가져온다.
        AppDomain currentDomain = AppDomain.CurrentDomain;
        //생성하려는 어셈블리의 이름을 설정한다.
        AssemblyName assemName = new AssemblyName("DynamicAssembly");         //어셈블리를 생성         AssemblyBuilder assemBuilder = currentDomain.DefineDynamicAssembly(assemName, AssemblyBuilderAccess.Run);
        //모듈 생성         ModuleBuilder moduleBuilder = assemBuilder.DefineDynamicModule("DynamicModule");         //클래스 생성         TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicClass"TypeAttributes.Public);         //메서드 생성         MethodBuilder methodBuilder = typeBuilder.DefineMethod("DynamicMethod"MethodAttributes.Public, nullnull);         //동적 코드 생성         ILGenerator msil = methodBuilder.GetILGenerator();         msil.EmitWriteLine("Dynamic Method Call");         msil.Emit(OpCodes.Ret);         //타입 정보 저장     DynamicType = typeBuilder.CreateType();     }
}

 

DynamicType이라는 필드가 있고, 이 필드는 동적으로 생성 할 Class의 Type을 저장하는 용도로 쓰입니다. 생성자에서는 동적으로 코드를 생성하기 위한 단계를 진행하는데요. 어셈블리->모듈->클래스->메서드->코드 생성의 단계로 진행을 하게 됩니다. 마지막에 보이는 ILGenerator 클래스를 이용하여 msil코드를 생성 할 수 있게 됩니다. 예제에서는 간단히 EmitWriteLine 메서드를 이용하여 "Dynamic Method Call"이라는 문자열을 화면에 뿌리는 코드를 생성 하였습니다.

 

#2. App 클래스 작성

App프로젝트로에서 CodeGenerator 프로젝트를 참조 추가 합니다.


				

App.cs 에 다음의 코드를 추가합니다.

class App
{
    static void Main(string[] args)
    {
        //CodeGenerator클래스 생성
        CodeGenerator.CodeGenerator codeGenerator = new CodeGenerator.CodeGenerator();
        //동적으로 추가 한 Type을 얻어옴
        Type DynamicType = codeGenerator.DynamicType;
        //Activator 클래스를 이용하여 인스턴스 생성
        object obj = Activator.CreateInstance(DynamicType);
        //동적으로 생성한 메소드를 얻어옴
        MethodInfo methodInfo = DynamicType.GetMethod("DynamicMethod");
        //실행
        methodInfo.Invoke(obj, null);
    } }

Main메서드의 로직은 간단합니다. 우선 CodeGenerator 클래스의 public 필드인 DynamicType에서 동적으로 생성한 Class의 Type을 얻어옵니다. Activator 클래스를 이용하여 클래스의 인스턴스를 생성하고, MethodInfo 클래스로 미리 생성한 DynamicMethod를 얻어와 실행합니다.

 

실행결과는 Dynamic Method Call이 나오면 의도한 결과이겠죠?

 


				

이상 동적으로 코드를 생성하고, Reflection을 이용하여 동적으로  인스턴스를 생성 / 실행하는 방법이었습니다. 

감사합니다.

 

소스코드 빌드 환경 : Windows7 x64, Visual Studio2010, .NET Framwork4.0

Posted by 맨날맑음
,

안녕하세요? 맨날맑음입니다.

일반적으로 실버라이트 프로젝트를 생성하면 위와 같이 두 개의 프로젝트가 생성됩니다. 하나는 실버라이트 프로젝트이고, 하나는 [프로젝트명].[Web]이 붙은 프로젝트인데요. Web이 붙은 프로젝트를 살펴보면 ASP.NET 프로젝트 인 것을 알 수 있습니다. 웹 프로젝트의 .aspx(웹폼)의 소스를 살펴보면 Active-X 처럼 object 태그를 이용하여 실버라이트를 웹 페이지에 로딩하는 것을 볼 수 있습니다.

이와 같이 실버라이트는 웹에서 동작하는 응용프로그램이고, 프로젝트의 상황에 따라 전체 페이지를 실버라이트로 만들 수도 있지만, ASP.NET으로 웹 페이지를 구성하고, 비주얼한 영역만 실버라이트로 제작하는 것이 일반적입니다.

 

이렇게 실버라이트를 웹 페이지의 특정 영역에 올려놓을 경우 실버라이트와 HTML간의 정보교환이라는 이슈가 생기기 마련인데요. 이번 포스팅에서는 실버라이트 <-> HTML 간의 엑세스 방법에 대해 알아보려 합니다.

 

#1. 실버라이트에서 HTML 접근

우선 실버라이트에서 HTML의 요소에 접근하는 방법을 알아 보기 위해서, 간단하게 HTML과 실버라이트에 컨트롤을 배치합니다.

위쪽의 흰색 배경이 HTML영역이고 아래쪽에 푸른 배경이 실버라이트 영역입니다. 위쪽 HTML영역의 글을 텍스트박스에 입력하고, Silverlight 영역의 버튼을 누르면 HTML의 정보를 실버라이트 텍스트박스에 넣는 시나리오 입니다.

 

<UserControl x:Class="HTMLBridge.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    >

    <StackPanel x:Name="LayoutRoot" Background="Azure">
        <TextBlock FontSize="14" Text="Silverlight 
영역"/>

        <StackPanel Orientation="Horizontal">
            <TextBox x:Name="txtHTML" Width="400" /> 
            <Button x:Name="btnGetHTML" 
                   Content="
버튼을 누르면 HTML 접근" 
                   Margin="10,0,0,0" 
                   Click="btnGetHTML_Click" />
        </StackPanel>
    </StackPanel>
</UserControl>

-MainPage.xaml-

 public partial class MainPage : UserControl
 {
     public MainPage()
        {
            InitializeComponent();            
        }
     private void btnGetHTML_Click(object sender, RoutedEventArgs e)
     {
         HtmlDocument htmlDocument = HtmlPage.Document;
         HtmlElement htmlElement = htmlDocument.GetElementById("htmlInputBox");
         string text = htmlElement.GetAttribute("value");

         if(text!=null)
         {
             this.txtHTML.Text = text;
         }
     }

-MainPage.xaml.cs-

<form id="form1" runat="server" style="height:100%">
<h2>HTML
영역</h2>
텍스트
 입력 : <input type="text" id="htmlInputBox" style="width:400px;" /><hr />
<div id="silverlightControlHost">
    <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">

……

-HTMLBridgeTestPage.aspx-

XAML에는 컨트롤의 배치가 선언 되어있고 btnGetHTML이라는 이름을 가진 버튼의 Click이벤트 핸들러가 정의되어 있습니다.비하인드 코드가 중요한데요. HtmlDocument 객체를 가져와서 GetElementById 메서드를 이용하여 HTML의 텍스트박스를 얻어 올 수 있습니다. HtmlElement 객체의 GetAttribute는 HTML요소의 속성값을 읽어 올 수 있습니다. 또한 SetPropertySetAttribute 메서드를 이용하여 값을 설정 할 수 있습니다.

실행시켜 보면 위와 같은 결과를 얻을 수 있습니다.

 

또한 HTML의 이벤트도 실버라이트에서 컨트롤 할 수 있는데요. AttachEvent메서드를 이용하면 됩니다.

public MainPage()
{
    InitializeComponent();
    HtmlDocument htmlDocument = HtmlPage.Document;
    HtmlElement htmlElement = htmlDocument.GetElementById("htmlInputBox");
    htmlElement.AttachEvent("onkeyup"new EventHandler(this.OnChange));
}
private void OnChange(object sender, EventArgs e) {     HtmlDocument htmlDocument = HtmlPage.Document;     HtmlElement htmlElement = htmlDocument.GetElementById("htmlInputBox");
   string text = htmlElement.GetAttribute("value");



   if (text != null)     {         this.txtHTML.Text = text;     } }
private void btnGetHTML_Click(object sender, RoutedEventArgs e)
{
    OnChange(nullnull);
}

- MainPage.xaml.cs -

테스트를 위해 AttachEvent 메서드를 이용하여, 생성자에 HTML의 텍스트 박스의 KeyUp 이벤트를 설정하였고, 이벤트 핸들러에서는 요소에서 가져온 텍스트를 실버라이트의 텍스트 박스에 설정합니다.

실행결과 HTML 텍스트박스의 글자가 바뀔 때 마다 실버라이트의 텍스트박스에 나타납니다.

#2. HTML에서 실버라이트 접근

HTML에서 실버라이트에 접근하는 시나리오를 위해 위와 같이 기존 .aspx에 버튼을 추가하고, 실버라이트 object요소에 id값을 추가합니다. 그리고 버튼의 클릭 속성에 자바스크립트 함수를 넣어줍니다.

<h2>HTML영역</h2>
    텍스트 입력 : <input type="text" id="htmlInputBox"   style="width:300px;" /> 	
<input type="button" id="htmlButton" onclick="CallSilverlight()" 
       value="버튼을 누르면 실버라이트에 접근" /><hr />
<div id="silverlightControlHost">
   <object id="ObjSilverlight" data="data:application/x-silverlight-2," 
            type="application/x-silverlight-2"
............

- HTMLBridgeTestPage.aspx -

실버라이트에 접근하려면 자바스크립트를 사용해야 하는데요. 버튼을 누르면 실버라이트의 텍스트박스에서 값을 가져와 HTML의 텍스트박스에 집어 넣도록 하겟습니다. 아래와 같이 자바스크립트를 <head>태그 안에 추가합니다.

<script type="text/javascript">
        function CallSilverlight() {
            var ObjSilverlight = document.getElementById("ObjSilverlight");
            var text = ObjSilverlight.Content.REG_SILVERLIGHT.SilverlightMethod();
            document.getElementById("htmlInputBox").value = text;
        }
    </script>

- HTMLBridgeTestPage.aspx –

자바스크립트를 살펴보면 document객체의 getElementById 함수에 실버라이트 Object태그의 id값을 넣어 실버라이트 객체를 얻어옵니다. 그 다음줄에서 ObjSilverlight.Content.REG_SILVERLIGHT.SilverlightMethod();와 같은 코드가 보이는데요, 이것이 핵심입니다.

 

자바스크립트를 통해 실버라이트에 접근하기 위해서는 RegisterSCriptableObject아래와 같이 객체를 등록 하여야 합니다. 객체를 등록하기 위해서는 객체에 [ScriptableMember]가 있어야 하고, 이 속성이 붙은 메서드는 자바스크립트에 노출됩니다.

 public MainPage()
 {
     InitializeComponent();
     ……
     HtmlPage.RegisterScriptableObject("REG_SILVERLIGHT"this);
 }
[ScriptableMember]  public string SilverlightMethod()  {
     return this.txtHTML.Text;  }

-MainPage.xaml.cs-

즉 위의 자바스크립트의 코드는 등록 된 실버라이트 객체의 ScriptableMemeber를 호출하는 코드입니다.

테스트를 해보니 HTML버튼을 누르면 자바스크립트가 호출되어, 실버라이트의 메서드를 호출하는 것을 알 수 있습니다.

 

실버라이트를 처음으로 접할 때 프로젝트를 생성해 보셨을 것입니다. 프로젝트의 구조를 살펴보고, ASP.NET이나 HTML위에 실버라이트가 Object태그로 추가되는 것을 보고, 한번쯤 둘간의 상호작용을 어떻게 할지에 대해 궁금하셨을 것입니다. 오늘은 가장 기본이 되는 실버라이트와 HTML간의 엑세스 방법에 대해 알아 보았습니다. 이 방법은 ASP.NET과 자바스크립트 기술을 이용하는 것이고 이것을 이용하여 실버라이트가 addOn된 멋진 페이지를 작성 할 수 있습니다.

Posted by 맨날맑음
,

안녕하세요? 맨날맑음 입니다.

이번 포스팅은 WPF에서 마우스 드래그를 이용한 간단한 사각형 그리기입니다. 이 예제를 응용하면 원이나 선도 그릴 수 있고, 자료구조를 이용하면 윈도우 그림판과 같은 기능도 쉽게 만들 수 있습니다.

-Fig1. 사각형을 그리는 WPF 어플리케이션-

Fig1에서 보는 바와 같이 마우스의 현재 좌표를 왼쪽 상단에 표시해 주며, 드래그를 이용하여 사각형을 그리는 기능을 합니다. WPF에서 제공하는 InkCanvas객체를 이용하면 더 간단하게 구현도 가능 하겠네요^^; 하지만 이번에는 Grid에 사각형을 그리는 방법으로 구현해 보겠습니다.

 

 

#1. 마우스 이벤트

-Fig2. WPF Window의 마우스 이벤트-

WPF의 컨트롤은 마우스 이벤트를 가지고 있습니다. 마우스 다운이나 업, 마우스 무브, 마우스 휠 등 이벤트를 지원하는데요. 우리 예제에서는 MouseLeftButtonDown과 MouseMove, MouseLeftButtonUp 세 가지 이벤트를 사용합니다. 마우스 이벤트 핸들러에서는 MouseEventArgs 객체가 인자로 넘어오고 이 이벤트객체를 이용하면 현재 마우스의 상태정보를 알 수 있습니다.

 

이름

설명

Device

이 이벤트를 시작한 입력 장치를 가져옵니다. (InputEventArgs에서 상속됨)

Handled

라우트된 이벤트가 경로를 따라 이동할 때의 현재 이벤트 처리 상태를 나타내는 값을 가져오거나 설정합니다.

LeftButton

마우스 왼쪽 단추의 현재 상태를 가져옵니다.

MiddleButton

마우스 가운데 단추의 현재 상태를 가져옵니다.

MouseDevice

이 이벤트와 연결된 마우스 장치를 가져옵니다.

OriginalSource

부모 클래스에서 Source를 조정하기 전에 순수 적중 테스트에 의해 결정되는 원본 보고 소스를 가져옵니다.

RightButton

마우스 오른쪽 단추의 현재 상태를 가져옵니다.

RoutedEvent

RoutedEventArgs 인스턴스와 연결된 RoutedEvent를 가져오거나 설정합니다. (RoutedEventArgs에서 상속됨)

Source

이벤트를 발생시킨 개체에 대한 참조를 가져오거나 설정합니다. (RoutedEventArgs에서 상속됨)

StylusDevice

이 이벤트와 연결된 스타일러스 장치를 가져옵니다.

Timestamp

이 이벤트가 발생한 시간을 가져옵니다. (InputEventArgs에서 상속됨)

XButton1

첫 번째로 확장된 마우스 단추의 현재 상태를 가져옵니다.

XButton2

두 번째로 확장된 마우스 단추의 현재 상태를 가져옵니다.

- MouseEventArgs 의 속성 (출처 : MSDN)-

이벤트 객체의 속성을 통해 마우스의 현재 상태정보를 알 수 있으며, GetPosition() 메서드를 이용하면 마우스 포인트의 좌표를 얻어 올 수도 있습니다.

 

#2. Shape객체

WPF에서 Rectangle, Ellipse, Polygon과 같은 모든 도형의 기본 클래스는 Shape입니다. Shape는 UI 요소이므로 패널이나 대부분의 컨트롤에서 사용 할 수 있습니다. Stroke 속성을 사용하면 도형의 테두리(윤곽선)의 색상을 지정 할 수 있고, StrokeThickness 속성을 이용하면 도형의 테두리 두께를 설정 할 수 있습니다. 또한 Fill 속성을 이용하면 도형의 표면 색상을 설정 할 수 있습니다.

 

-Fig3. 여러 속성을 적용하여 랜더링 된 원의 모습-

 

#3. 코드 구현

코드가 간단하여, 대부분의 설명은 주석으로 코드설명을 대신합니다. WPF 프로젝트를 생성하고, 다음과 같은 코드를 넣어 줍니다.

 

    

-Code1. MainWindow.xaml-

 

 
public partial class MainWindow : Window
 {
     /// 
     /// 드레그를 시작한 마우스 좌표;
     /// 
     Point prePosition;
     /// 
     /// 현재 그려지는 사각형
     /// 
     Rectangle currentRect;

     public MainWindow()
     {
         InitializeComponent();
         //이벤트 핸들러 생성
         this.root.MouseLeftButtonDown += new MouseButtonEventHandler(root_MouseLeftButtonDown);
         this.root.MouseMove += new MouseEventHandler(root_MouseMove);
         this.root.MouseLeftButtonUp += new MouseButtonEventHandler(root_MouseLeftButtonUp);
         
         // 좌표를 나타내는 TextBlock을 최상위로 ...
         Grid.SetZIndex(this.tbPosition, 99999);
     }

     /// 
     /// 마우스 클릭 해제 이벤트 핸들러
     /// 
     /// 
     /// 
     void root_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
     {
         //마우스 캡춰를 제거한다.
         this.root.ReleaseMouseCapture();
         SetRectangleProperty();
         currentRect = null;
     }

     private void SetRectangleProperty()
     {
         //사각형의 투명도를 100% 로 설정
         currentRect.Opacity = 1;
         //사각형의 색상을 지정
         currentRect.Fill = new SolidColorBrush(Colors.LightYellow);
         //사각형의 테두리를 선으로 지정
         currentRect.StrokeDashArray = new DoubleCollection(); ;
     }

     /// 
     /// 마우스 이동 이벤트 핸들러
     /// 
     /// 
     /// 
     void root_MouseMove(object sender, MouseEventArgs e)
     {
         
         //현재 이동한 마우스의 좌표를 얻어온다
         Point currnetPosition = e.GetPosition(this.root);
         //좌표를 표시한다.
         this.tbPosition.Text = string.Format("마우스 좌표 : [{0},{1}]", currnetPosition.X, currnetPosition.Y);
         //마우스 왼쪽 버튼이 눌려있으면
         if (e.MouseDevice.LeftButton == MouseButtonState.Pressed)
         {
             if (currentRect != null)
             {
                 //사각형이 나타날 기준점을 설정한다.
                 double left = prePosition.X;
                 double top = prePosition.Y;
                 //마우스의 위치에 따라 적절히 기준점을 변경한다.
                 if (prePosition.X > currnetPosition.X)
                 {
                     left = currnetPosition.X;
                 }
                 if (prePosition.Y > currnetPosition.Y)
                 {
                     top = currnetPosition.Y;
                 }
                 //사각형의 위치 기준점(Margin)을 설정한다
                 currentRect.Margin = new Thickness(left, top, 0, 0);
                 //사각형의 크기를 설정한다. 음수가 나올 수 없으므로 절대값을 취해준다.
                 currentRect.Width = Math.Abs(prePosition.X - currnetPosition.X);
                 currentRect.Height = Math.Abs(prePosition.Y - currnetPosition.Y);
             }
         }
     }

     /// 
     /// 마우스 왼쪽 버튼 클릭 이벤트 핸들러
     /// 
     /// 
     /// 
     void root_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
     {
         //마우스의 좌표를 저장한다.
         prePosition = e.GetPosition(this.root);
         //마우스가 Grid밖으로 나가도 위치를 알 수 있도록 마우스 이벤트를 캡처한다.
         this.root.CaptureMouse();
         if (currentRect == null)
         {
             //사각형을 생성한다.
             CreteRectangle();               
         }
     }

     private void CreteRectangle()
     {
         
         currentRect = new Rectangle();
         currentRect.Stroke = new SolidColorBrush(Colors.DarkGreen);
         currentRect.StrokeThickness = 2;
         currentRect.Opacity = 0.7;
         //사각형을 그리는 동안은 테두리를 Dash 스타일로 설정한다.
         DoubleCollection dashSize = new DoubleCollection();
         dashSize.Add(1);
         dashSize.Add(1);
         currentRect.StrokeDashArray = dashSize;
         currentRect.StrokeDashOffset = 0;
         //사각형의 정렬 기준을 설정한다.
         currentRect.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
         currentRect.VerticalAlignment = System.Windows.VerticalAlignment.Top;
         //그리드에 추가한다.
         this.root.Children.Add(currentRect);
     }
 }

-Code2. MainWindow.xaml.cs-

마우스를 드래그하는 동안은 그림판이나 포토샵에서와 같이 점선으로 사각형의 모양을 표시 해 줄 필요가 있기 때문에 테두리의 속성을 변경 할 필요가 있습니다. 테두리를 점선으로 표시하는 속성은 Rectangle객체의 StrokeDashArray를 사용하여 설정 할 수 있으며, XAML에서는 다음과 같이 표현 할 수 있습니다.

 

-Fig4. StrokeDashArray 속성이 적용 된 Ellipse-

소스 빌드 환경 : VisualStudio2010, .NET Framework4.0

Posted by 맨날맑음
,

작성중

일반적으로 동적인 웹 사이트 개발 시 상태관리(State Management)가 필요 합니다. 이번 포스팅에서는  상태관리가 무엇인지와 우리가 일반적으로 많이 사용하는 쿠키(Cookie)와 세션(Session)에 대해 자세히 알아 보도록 하겠습니다.

 

#1. 웹 서버 동작 원리

웹 사이트 상태관리의 필요성을 알기 위해 웹 서버와 클라이언트(웹 브라우저)가 어떠한 방식으로 동작 하는지를 먼저 알아 보겠습니다.

image-Fig1. 정적 웹 서버 동작 원리-

 

Fig1을 보면 아주 간단한 원리로 동작 하는 것을 알 수 있는데요. 클라이언트가 HTTP 프로토콜을 사용하여 웹 서버로 요청(Request)을 하게 되면 웹 서버는 해당 하는 HTML을 웹 브라우저로 전송 해 주고 웹 브라우저는 응답(Response) 받은 HTML을 파싱하여 사용자에게 보여주게 됩니다. 여기서 ‘요청’이란 우리가 웹 브라우저의 주소 표시줄에 http://crynut84.tistory.com이라고 입력 하는 행위나 웹 사이트의 링크를 마우스로 클릭하는 행위 등을 말합니다.

 

웹 사이트는 다시 정적인 웹 사이트와 동적인 웹사이트로 나눌 수 있습니다. 정적인 웹 사이트는 웹 서버에서 .HTML 파일을 완성된 .HTML 파일을 가지고 있다가 클라이언트의 요청이 있을 시 해당하는 .HTML 파일을 돌려 주는 형태입니다. 당연히 HTML 페이지의 내용들은 이미 결정 되어있어 있으므로 클라이언트의 상태, 방문정보, 시각등의 내용에 관계없이 항상 동일한 HTML 페이지를 보여줍니다. 요즘은 이런 웹사이트는 거의 없을 듯 합니다.

 

동적인 웹 사이트는 사용자의 액션에 따라 웹 사이트가 다르게 동작 하는 것을 말합니다. 예를들어 요즘 웹 사이트는 거의 로그인이라는 인증 과정을 거치게 되고, 로그인을 하면 ‘전호진님 환영합니다’라고 환영 인사도 해줍니다. 또한 게시판은 검색어에 따라 다른 결과를 보여주게 되고, 사용자 입맛에 맛게 정렬도 할 수 있습니다. 이와같이 요즘 일반적으로 볼 수 있는 사용자와 웹사이트가 서로 상호작용을 하는 웹 사이트를 동적인 웹사이트라고 합니다.

 

동적인 웹사이트를 만드는 기술은 ASP.NET, JSP, ASP, PHP, CGI등 여러 가지가 있습니다. 마이크로소프트의 동적인 웹사이트를 만드는 기술인 ASP.NET의 동작 원리(Fig2)를 보면 정적 웹사이트와 조금 다른 것을 알 수 있는데요.

image-Fig2. 동적 웹 서버 동작 원리-

 

클라이언트가 요청하게 되면 웹 서버는 요청에 대한 적절한  HTML을 새롭게 생성 합니다. 요청에 대한 일련의 처리(로직 수행)를 수행하게 되고 처리 결과를 다시 클라이언트에 돌려 주게 되는데 동적인 웹 사이트라고 해도 클라이언트가 받는 최종 응답은 정적인 HTML 페이지입니다. 예를들어 게시판에 여러 게시글이 있는데 사용자가 ASP.NET을 검색어로 입력하고 검색 버튼을 누르게 되면 이 요청을 웹서버가 받아서 게시글 중 ASP.NET이 들어간 결과만 찾아서 형식에 맞는 HTML을 구성하게 되고 클라이언트에 응답하주게 되는 것입니다. 이렇게 동적인 처리를 위해 ASP.NET과 같은 웹 기술이 필요하게 되고, 모든 처리는 웹 서버에서 수행되며, 클라이언트의 입장에서는 정적인 웹 사이트와 동일하게 웹 서버에 요청을 하고 응답받은 결과를 파싱하여 보여 주게 됩니다.

 

#2. 상태 관리(State Management)

웹 사이트를 사용하는 사용자는 HTTP GET방식이나 HTTP POST방식을 사용하여 웹 서버로 페이지를 요청하게 되고, 웹 브라우저는 웹 서버에서 응답받은 HTML을 파싱하여 렌더링하게 됩니다. 이러한 일련의 주기를 ‘라운드트립(Round Trip)’ 이라고 합니다.그런데 HTTP 프로토콜은 상태를 저장 할 수 없는 프로토콜입니다. 이 말은 클라이언트의 요청이 왔을대 웹 서버는 해당 요청에 대한 응답인 HTML을 보내주고 연결을 끊어버린다는 것입니다. 그렇기 때문에 라운드트립 시 페이지에 있는 컨트롤의 사용자 입력정보나 페이지의 정보들이 모두 손실 되게 됩니다.  예전에 사용하던 정적인 웹사이트(일방적으로 보기만 하는 웹사이트)에서는 문제가 되지 않지만 동적인 웹사이트에서는 사용자의 요청이나 컨트롤의 입력정보를 유지할 필요가 있기 때문에 ASP.NET은 상태 관리를 할 수 있는 여러가지 기능을 제공합니다.

 

상태관리의 정보를 저장하는 장소에 따라 두가지로 분류 할 수 있는데, 웹 서버에 저장하는 경우와, 클라이언트에 저장하는 경우가 있습니다.

▶ 상태 정보를 클라이언트에 저장하는 방식

  • 뷰 상태(View State)
  • 컨트롤 상태(Control State)
  • 숨겨진 필드(Hidden Field)
  • 쿠키(Cookie)
  • 쿼리 문자열(Query String)

상태 정보를 웹 서버에 저장하는 방식

  • 응용 프로그램 상태(Application State)
  • 세션(Session)
  • 프로필 속성(Profile Property)
  • 데이터베이스(Database)

두가지 방식은 장단점이 존재 하는데요. 일반적으로 데이터가 작고, 중요하지 않은(보안 등의 이유)정보는 클라이언트 측에서 관리하고, 나머지는 웹 서버에서 관리 하는 것이 좋습니다. 여러가지 상태관리 기능 중 Cookie와 Session에 관해 자세히 알아보겠습니다.

 

#3. 쿠키(Cookie)

-Fig3. 맛있는 쿠키-

 

쿠키는 요청 및 응답하는 과정에 포함되는 텍스트 정보이고 쉽게 구현하여 사용 할 수 있는 방법중에 하나입니다. 일반적인 브라우저에서 최대 4KB의 텍스트 정보를 담을 수 있으며 하나의 사이트는 20개만 허용되며, 모든 사이트를 통틀어서 300개로 제한 됩니다. 만약 이 범위를 넘어 더 많이 저장 하려고 할 경우에는 가장 오래된 쿠키부터 삭제되므로 사용의 주의해야하고, 4KB로 비교적 작은 크기만을 저장하기 때문에 적은 양의 데이터나 ID, 최근 읽은 글, 최근 본 상품과 같은 식별자를 저장할 때 사용하는 것이 적합합니다.

 

1. 쿠키의 동작 방식

쿠키는 서버에서 생성하여 클라이언트의 브라우저에서 관리하기 때문에 요청시 쿠키를 생성하고 브라우저를 닫을때 쿠키를 파괴합니다. 응답을 통해 얻어온 쿠키는 만료시간 여부에 따라 클라이언트의 PC에 파일로 저장하게 됩니다.

 

image

-Fig4. 웹 사이트를 처음 요청 할 경우 쿠키 생성 -

 

쿠키의 생성 주기를 살펴보면처음으로 페이지를 요청 할 경우 웹 서버에서는 쿠키를 생성하게 되고, 페이지를 돌려 줄때 HTTP 헤더에 쿠키를 포함하여 돌려 주게 됩니다. 이렇게 넘겨 받은 쿠키는 클라이언트에서 관리 하고 있다가, 다음번 요청때 쿠키를 함께 전송하게 되고, 서버에서는 쿠키 정보를 읽어 이전 상태 정보를 알 수 있게 됩니다. 이때 웹 서버는 Fig5에서 처럼 정보를 변경 할 필요가 있을때 쿠키를 업데이트하여 다시 변경된 쿠키와 함께 응답하게 됩니다.

image -Fig5. 쿠키를 가지고 있을 경우 쿠키 활용-

2. ASP.NET에서 쿠키 사용

쿠키는 서버로 부터 응답받은 사항이기 때문에 HttpResponse 객체를 통해 클라이언트로 전송 됩니다. 쿠키에 정보를 저장하는 방법은 두가지가 있습니다. Page클래스의 Response 객체를 사용하는 것과 HttpCookie 클래스를 사용하는 방법입니다.

//Response 사용 
Response.Cookies["쿠키명1"].Value = "쿠키 예제;
Response.Cookies["쿠키명1"].Expires = DateTime.Now.AddMinutes(30); 

//HttpCookie 클래스 사용 HttpCookie cookie = new HttpCookie("쿠키명2");
cookie.Value = "쿠키 예제";
cookie.Expires = DateTime.Now.AddSeconds(30); 
Response.Cookies.Add(cookie);

-Code1. 쿠키 저장-

쿠키명1과 쿠키명2라는 이름을 사용하는 쿠키를 생성 하였고 각각 Value 속성을 통해 상태유지에 필요한 값을 넣어 주었습니다. Expires 속성은 만료 기간인데, 이 속성을 설정하면 쿠키가 클라이언트의 컴퓨터의 파일의 형태로 저장됩니다.  만료기간을 설정하지 않은 쿠키는 브라우저의 메모리에서 관리됩니다. Code1에서 쿠키명1은 30분후에 쿠키를 지우며, 쿠키명2는 30초 후에 쿠키를 지우도록 설정 하였습니다. 만료시간이 지난 쿠키는 클라이언트가 쿠키를 생성한 웹 사이트에 다시 요청을 보낼때 삭제됩니다.

Posted by 맨날맑음
,

안녕하세요? 맨날맑음 입니다.

Windows Form Application에서 WPF로 만든 Windows Application을 호출하려 합니다.

WPF의 Control을 윈폼에 호스팅 하기 위해서는 ElementHost를 사용하면 되는데요(이에 관한 강좌는 추후에 포스팅 하겠습니다.)
오늘은 윈폼에서 WPF의 창(Window)를 띄우려고 합니다.

#1. 프로젝트 생성

우선 Windows Forms 응용프로그램 프로젝트와 WPF 사용자 정의 컨트롤 라이브러리 프로젝트 2개를 생성합니다.

 

#2. DLL 참조

윈폼에서 WPF 윈도우를 호출하기 위해 WPF 프로젝트의 DLL(wpfControlLibrary1)과 윈폼에서 WPF 사용을 위한 DLL을 참조 합니다.

Windows Form에서 WPF를 사용하기 위한 DLL 목록
  PresentationCore
  PresentationFramework
  System.Xaml
  WindowsBase


#3. WPF 윈도우 호출
이제 Windows Form의 소스코드에 WPF 윈도우 호출 코드를 넣습니다.(저는 간단히 버튼을 누를 때 WPF 창이 나오도록 하겠습니다)

private void button1_Click(object sender, EventArgs e)

 {

     Window1 WPF_Wnd = new Window1();

     WPF_Wnd.Show();

 }

#4. 결과 확인 

간단한 방법으로 Windows Form에서 WPF 윈도우를 호출 할 수 있습니다. 감사합니다.

Posted by 맨날맑음
,

안녕하세요? 맨날맑음 입니다.

 

이번에 만들어 볼 내용은 Microsoft Office Object를 이용한 간단한 PPT Viewer를 만드는 것입니다.

제가 2010년 HOONS.NET WPF 시삽으로 활동하게 되면서 어플리케이션은 WPF로 만들어 보겠습니다.

이 방법의 대략적인 과정을 살펴보면 다음과 같습니다.

 

1. Microsoft Office 12.0 Object Library를 참조한다.(Office 2007 버전에 경우 12.0)

2. ApplicationClass 객체를 이용하여 PPT 파일을 읽어온다.

3. Presentation 객체를 이용하여 슬라이드를 그림 파일로 하드에 저장한다.

4. 그림 파일을 로딩하여 마치 PTT Viewer인척(?) 한다.

 

전체적인 과정에서 보듯이 PPT파일을 읽어드려 하드에 그림파일로 저장한 것을 띄우는 방식이기 때문에 PPT에 들어있는 애니메이션 효과를 기대하기는 어렵습니다. 그래서 PTT Viewer인척(?)이라고 한 것 이구요.

 

#1. 관련 어셈블리 참조하기


우선 PPT Viewer를 구현하기 위해 두가지 객체가 필요합니다.

 

COM으로 이루어진 Microsoft Office 12.0 Object Library에 포함된 ApplicationClass 객체가 필요하구요. .NET 어셈블리중 Microsoft.Office.Interop.PowerPoint 12.0에 포함된 Presentation 객체가 필요합니다. 두가지 어셈블리를 참조하고 using도 추가해 줍니다.

 

using Microsoft.Office.Interop.PowerPoint;
using Microsoft.Office.Core;

 

- Fig1. 관련 어셈블리 참조 -

 

 

#2. 소스 구현

string Path;

Presentation PPT;


private
void ReadPPTfile()

{

    ApplicationClass app = new ApplicationClass();

    PPT = app.Presentations.Open(Path, MsoTriState.msoTrue, MsoTriState.msoFalse, MsoTriState.msoFalse);

    app.Quit();

}      

 

private void MakePPTIamge()

{

    string myPicturesPath = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);

    for (int i = 0; i < PPT.Slides.Count; ++i)

    {               

        PPT.Slides[i + 1].Export(string.Format("{0}\\temp{1}.jpg", myPicturesPath, i), "JPG",

            (int)PPT.Slides[i + 1].Master.Width, (int)PPT.Slides[i + 1].Master.Height);               

    }

}

 

소스는 간단합니다. ReadPPTfile()에서 ApplicationClass 객체를 이용하여 ppt 파일을 Open하고 Presentation 객체를 return 받아 놓습니다. MakePPTIamge()에서 PPT.Slides.Export() 메서드를 호출하여 적당한 경로에 jpg 파일(다른 포맷도 지정 가능)로 저장하는 방법입니다! PPT.Slides.Count를 이용하여 읽어들인 슬라이드 갯수도 알 수 있습니다.

이제 동작에 따라 적당한 그림파일을 로딩하여 사용하면 초간단 PPT Viewer 완성입니다^^;


#3. 마치면서..

요방법의 단점을 좀 생각해 보면. 앞서 이야기한 애니메이션 효과는 사용 할 수 없다는 것과.. 많은양의 슬라이드를 로딩할 경우 오래 걸린다는 단점이 있습니다. 하지만 간단한 방법으로 구현 할 수 있고, 웹으로 뷰어를 제공 할 경우 더 어울리는 방법 일 것 같네요.

'.NET > WPF' 카테고리의 다른 글

마우스 드래그를 이용한 사각형 그리기  (6) 2010.09.13
Windows Form에서 WPF Window Application 호출하기  (3) 2010.06.28
[WPF] Widget 만들기!  (0) 2008.12.26
Posted by 맨날맑음
,
안녕하세요? 맨날맑음 입니다.

정말 오랜만에 블로깅이네요. 3개월 정도 지난거 같네요. 바쁘다는 핑계로 블로깅도 미루고, 덕분에 블로깅 할 주제는 많이 생겨서 좋은건가요?

- Fig1. 곰플레이어에서 파일 Drag&Drop을 이용하여 실행시키는 장면 -

 
많은 어플리케이션이 위와같이 파일을 Drag&Drop만으로 열 수 있는 기능을 제공해 주고있습니다. 실제 파일을 다루는 어플리케이션을 개발하다보면 빠지면 서운한 기능일텐데요. .Net WinForm에서는 아주 쉬운방법으로 위와 같은 기능을 구현 할 수 있습니다.

#1. Windows Forms 프로젝트 생성.

- Fig2. Windows Forms 프로젝트 생성 -


#2. ListBox 추가

- Fig3. Form에 ListBox를 추가한 모습 -

여기서 ListBox는 Form위로 드레그 되어 들어온 파일의 정보를 보기위한 간단한 용도입니다.

#3. 드레그 드롭 관련 지식
public virtual bool AllowDrop { get; set; }
- 사용자가 컨트롤로 끌어 온 데이터가  컨트롤에서 허용되는지 여부를 나타내는 값을 가져오거나 설정.
- 반환 값: 끌어서 놓기 작업을 수행할 수 있으면 true이고, 그렇지 않으면 false, 기본값은 false


Controls의 AllowDrop 프로퍼티를 이용하면 Control에 Drag로 데이터를 끌어올 수 있는지 없는지 설정 할 수 있습니다. 이말은 즉 Controls를 상속받는 많은 WinForm과 Control들이 AllowDrop 프로퍼티를 사용 할 수 있다는 말입니다.

Drag&Drop 관련 이벤트
Controls.DragDrop
: 드레그 작업이 완료되면 발생.
Controls.DragOver : 드레그한 개체가 컨트롤 위로 올라오면 발생.
Controls.DragEnter : 드레그한 개체가 컨트롤 범위 안으로 들어오면 발생.
Controls.DragLeave : 드레그한 개체가 컨트롤 범위 밖으로 나가면 발생.

4개의 이벤트가 있는데요. 우리가 사용 할 이벤트는 DragDrop과 DragOver 두개 입니다.

DragEventHandlerDragEventArgs
X : 화면 좌표로 나타난 마우스 포인터의 x좌표.
Y : 화면 좌표로 나타난 마우스 포인터의 Y좌표.
Data.GetDataPresent : 인스턴스에 저장된 데이터가 지정된 형식과 관련 있는지 확인.
Effect : 드레그한 개체에대한 마우스 커서를 결정.

이벤트관련 클래스의 사용법도 알아보았으니 이제 구현을 해봅니다!

#4. 코드 구현

using System.Windows.Forms;

 

namespace DragSample

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

            //드레그를 허용

            this.AllowDrop = true;

            //Drag관련이벤트 연결------------------------------------------------

            this.DragOver += new DragEventHandler(Form1_DragOver);

            this.DragDrop+=new DragEventHandler(Form1_DragDrop);           

        }

 

        //드레그한 개체가 폼위로 올라올때--------------------------------------

        void Form1_DragOver(object sender, DragEventArgs e)

        {

            //드레그하는 개체가 파일이면

            if (e.Data.GetDataPresent(DataFormats.FileDrop))

            {

                //마우스 커서를 Copy모양으로 바꿔준다.

                e.Effect = DragDropEffects.Copy;

            }

            else

            {

                //아닐경우 커서의 모양을 θ 요런 모양으로 바꾼다.

                e.Effect = DragDropEffects.None;

            }

        }

 

        //드레그한 개체를 폼위에 올려 놓았을때----------------------------------

        void Form1_DragDrop(object sender, DragEventArgs e)

        {

            //객체들의 이름을 string 배열에 얻어온다.

            string[] files = e.Data.GetData(DataFormats.FileDrop) as string[];

            if(files!=null)

            {

                foreach (string file in files)

                {

                    //끌어온 파일명을 리스트박스에 달아준다.

                    listBox1.Items.Add(file);

                }               

            }

        }

    }

}

간단한 소스라 따로 설명은 없어도 될 것 같습니다. 유의 할 점은 e.Data.GetData(DataFormats.FileDrop)에서 반환값이 string 배열이라는 점입니다.

이와같은 방법을 응용하면 ListBox간 아이템 이동이라던지, 대부분의 Drag&Drop 기능은 구현 가능 할 것 같습니다.
Posted by 맨날맑음
,

안녕하세요? 맨날맑음입니다.

화살표를 그려야 할 일이 생겨서 어떻게 그릴까 생각하다가, Line을 여러개 그려서 화살표를 만들려고 했습니다.
선의 각도에 따라 화살표 머리 부분의 각도가 변하기 때문에 수학을 못하는 저로써는 화살표 그리는 것 조차도 힘든일이 었습니다.

하지만! .NET의 Pen 클래스는 Line의 끝점들의 모양을 변경시켜 주는 기능을 제공합니다.
Pen 클래스의 좀더 자세한 설명은 아래의 MSDN 도움말을 참조 하시면 됩니다.
http://msdn.microsoft.com/ko-kr/library/system.drawing.pen(en-us,VS.85).aspx

MSDN에서 Pen 클래스의 속성을 보면 StartCap 과 EndCap이 있습니다. 이것이 선의 끝모양을 지정하는 속성입니다. 그럼 선의 시작과 끝 모양을 화살표로 바꾸어 보겠습니다.

Pen pen = new Pen(Color.Blue, 6);
pen.StartCap = LineCap.ArrowAnchor;
pen.EndCap = LineCap.ArrowAnchor;
위의 소스에서 처럼 Pen 객체를 생성하고, 해당하는Cap 속성을 바꿔주는 아주 간단한 방법으로 끝모양을 바꿔줄 수 있습니다.  LineCap 사용을 위해서는 using System.Drawing.Drawing2D;을 해주셔야 합니다.
화살표 모양 이외에도 여러 모양을 지정 할 수 있습니다. LineCap 을 살펴 보면 열거형으로 되어 있습니다. 각각의 멤버의 의미를 보면 다음과 같습니다.
Flat : 일직선 형태의 끝 모양을 지정합니다.
Square : 정사각형 형태의 선 끝 모양을 지정합니다.
Round : 둥근 선 끝 모양을 지정합니다.
Triangle : 삼각형 선 끝 모양을 지정합니다.
NoAnchor : 앵커를 지정하지 않습니다.
SquareAnchor : 정사각형 앵커 선 끝 모양을 지정합니다.
RoundAnchor : 둥근 앵커 끝 모양을 지정합니다.
DiamondAnchor : 다이아몬드 앵커 끝 모양을 지정합니다.

ArrowAnchor : 화살표 모양의 앵커를 지정합니다.
Custom : 사용자 지정 선 끝 모양을 지정합니다.

AnchorMask : 선 끝 모양이 앵커 모양인지 여부를 검사하는 데 사용되는 마스크를 지정합니다.
아래와 같이 간단하게 Windows Form 프로젝트를 생성해서 Paint 이벤트 핸들러에서 화살표를 그려 보겠습니다.

using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.Paint += new PaintEventHandler(Form1_Paint);
        }

        void Form1_Paint(object sender, PaintEventArgs e)
        {
            Pen pen = new Pen(Color.Blue, 6); //Pen 객체 생성
            pen.StartCap = LineCap.ArrowAnchor; //Line의 시작점 모양 변경
            pen.EndCap = LineCap.ArrowAnchor; //Line의 끝점 모양 변경
            e.Graphics.DrawLine(pen, 20, 50, 300,50); //Line 그리기
        }
    }
}
 
이와같이 복잡한 화살표 알고리즘 없이 Pen 클래스의 속성 변경 만으로도 간단하게 화살표를 그릴 수 있습니다.
참 쉽죠잉~
Posted by 맨날맑음
,

안녕하세요? 맨날맑음 입니다.

회원가입 기능을 구현하다 보면, 주민등록번호가 올바른지 체크해야 하는 경우가 생기게 됩니다.
우선 주민등록번호가 어떠한 형태(?)로 이루어 졌는지 알아 볼 필요가 있습니다.

주민등록번호는 총 13자리 숫자로 구성되어 있습니다.
모두 아시는 바와같이 앞의 6자리는 태어난 날의 년, 월, 일을 나타냅니다  
뒤의 7자리는 조금 복잡한데요. 
 # 1번 자리 : 성별 (ex. 남자 : 1또는3, 여자 : 2또는4)
    - 3과 4는 낮설겠지만 00년 이후 출생자부터는 남자는 3 여자는 4입니다.
 # 2~5번 자리 : 출생 신고 당시의 거주지 관할 동사무소의 지역코드
 # 6번 자리 : 출생 신고 날짜
 # 7번 자리 : 검정 코드
    - 7번 자리가 매우 중요합니다. 이것은 앞의 12자리의 유효성을 검증해 줄 매직넘버(?)로 쓰이게 됩니다. 

주민등록번호의 유효성을 알아 보기 위해서는 각 자리수의 미리 지정된 값을 곱하여, 곱한 결과를 전부 더하고,
그 값을 11로 나눈 후, 그 결과 값(나머지)11에서 빼어 13번째 자리수인 검정 코드와 일치하면 유효하게 됩니다.
참.. 말로 할려니 쉽지 않습니다.

주민등록번호가 123456 - 1234567 이라고 예를 들어 보겠습니다.
# 1. 아래와 같이 검정 코드인 7을 제외한 각자리에 미리 지정된 값(234567892345)을 각각 곱하여 줍니다.

# 2. 각 자리에서 곱한 결과를 모두 더해 줍니다.

# 3. 더한 결과(206)을 11로 나누어 줍니다(% 나머지 연산).

# 4. 11에서 나머지의 결과를 빼줍니다.

# 5. 나머지 3과 검증코드 7이 같은지 확인 합니다. 같으면 유효한 주민등록번호입니다. 이 경우 11에서 10을 뺀다던지, 1을 빼면 2자리의 결과가 나오게 됩니다. 그럴때는 뒤의 1자리만 취하여 비교하시면 됩니다. (이와같은 이유로 4번의 결과에 % 연산으로 10을 한번 더해주면 뒤의 1자리만 취할 수 있겠죠?)

# 결론 . 주민등록번호 123456 - 1234567는 틀린 주민번호 입니다!


C# 코드로 만들어 보겠습니다.
코드 작성시 고려 사항
  # 1. 주민등록번호가 13자리 인가?
  # 2. 13자리 모두 숫자인가?
  # 3. 위의 공식에 부합하는가?

bool IsAvailableRRN(string RRN)

{

    //공백 제거

    RRN = RRN.Replace(" ", "");

    //문자 '-' 제거

    RRN = RRN.Replace("-", "");

    //주민등록번호가 13자리인가?

    if (RRN.Length != 13)

    {

        return false;

    }

 

    int sum = 0;

    for (int i = 0; i < RRN.Length - 1; i++)

    {

        char c = RRN[i];

        //숫자로 이루어져 있는가?

        if (!char.IsNumber(c))

        {

            return false;

        }

        else

        {

            if (i < RRN.Length)

            {

                //지정된 숫자로 자리를 나눈 더한다.

                sum += int.Parse(c.ToString()) * ((i % 8) + 2);

            }

        }

    }

    // 검증코드와 결과 값이 같은가?

    if (!((((11 - (sum % 11)) % 10).ToString()) == ((RRN[RRN.Length - 1]).ToString())))

    {

        return false;

    }

    return true;

}

Posted by 맨날맑음
,
안녕하세요? 맨날맑음 입니다.

몇몇 프로그램을 보면, 윈도우끼리 도킹 기능으로 서로 일정거리 만큼 가까워지면 딱 달라붙게 만들어진 것을 볼 수 있습니다. 이런 기능의 명칭을 정확히 몰라서; 일단 제목은 '자석 윈도우'라고 정해 보았는데요;

밑의 예(이스트소프트의 알송)에서 볼 수 있듯이 각 윈도우끼리는 가까이 가면 달라 붙고, 제일 위의 윈도우를 이동하면 붙어있는 윈도우도 따라서 이동하게 됩니다.
이런 기능을 간단하게 Windows Form을 이용하여 만드는 방법을 알아보려 합니다. 이런 기능을 제공하는 Class Library가 있을까 찾아 보다가 결국 찾지는 못하고, 한 외국 사이트에 이와같은 소스를 발견하였습니다. 현재 출처를 정확하게 기억 나지 않습니다. 그래서 완성된 소스는 올리지 않습니다.(필요하신분은 요청하세요)
Form1.cs
 private void Form1_Move(object sender, EventArgs e)
        {
            if (isForm2Docked)
            {
                tempForm2.SetDesktopLocation(tempForm2.Location.X + (this.Location.X - prevLoc.X),
                    tempForm2.Location.Y + (this.Location.Y - prevLoc.Y));
            }
            prevLoc.X = this.Location.X;
            prevLoc.Y = this.Location.Y;
        }
 
메인이 되는 폼의 Move 이벤트에서는 현재 서브폼이 붙어있는지 확인하여, 붙어있으면 위치를 함께 옮겨줍니다.

Form2.cs
private void Form2_Move(object sender, EventArgs e)
        {
            bool amIDocked = false;
            Point newPoint = GetNewFormPosition(this, passedInForm, out amIDocked);
            this.SetDesktopLocation(newPoint.X, newPoint.Y);
            ((Form1)passedInForm).isForm2Docked = amIDocked;
        }

        public Point GetNewFormPosition(Form thisForm, Form parentForm, out bool isDocked)
        {
            int[] xRange = new int[2] { parentForm.Location.X, parentForm.Location.X + parentForm.Width };
            int[] yRange = new int[2] { parentForm.Location.Y, parentForm.Location.Y + parentForm.Height };
            int xGap = Math.Abs(thisForm.Location.X - parentForm.Location.X);
            int yGap = Math.Abs(thisForm.Location.Y - parentForm.Location.Y);
            int leftGap = Math.Abs((thisForm.Location.X + thisForm.Width) - parentForm.Location.X);
            int rightGap = Math.Abs(thisForm.Location.X - (parentForm.Location.X + parentForm.Width));
            int topGap = Math.Abs((thisForm.Location.Y + thisForm.Height) - parentForm.Location.Y);
            int bottomGap = Math.Abs(thisForm.Location.Y - (parentForm.Location.Y + parentForm.Height));
            int xNew = thisForm.Location.X;
            int yNew = thisForm.Location.Y;
            isDocked = false;

            if ((leftGap <= dockGap) && (((thisForm.Location.Y >= yRange[0]) && (thisForm.Location.Y <= yRange[1])) ||
                ((thisForm.Location.Y + parentForm.Height >= yRange[0]) && (thisForm.Location.Y + parentForm.Height <= yRange[1]))))
            {
                xNew = parentForm.Location.X - thisForm.Width;
                if (yGap <= dockGap) yNew = parentForm.Location.Y;
                isDocked = true;
            }
            if ((rightGap <= dockGap) && (((thisForm.Location.Y >= yRange[0]) && (thisForm.Location.Y <= yRange[1])) ||
                ((thisForm.Location.Y + parentForm.Height >= yRange[0]) && (thisForm.Location.Y + parentForm.Height <= yRange[1]))))
            {
                xNew = parentForm.Location.X + parentForm.Width;
                if (yGap <= dockGap) yNew = parentForm.Location.Y;
                isDocked = true;
            }
            if ((topGap <= dockGap) && (((thisForm.Location.X >= xRange[0]) && (thisForm.Location.X <= xRange[1])) ||
                ((thisForm.Location.X + parentForm.Width >= xRange[0]) && (thisForm.Location.X + parentForm.Width <= xRange[1]))))
            {
                yNew = parentForm.Location.Y - thisForm.Height;
                if (xGap <= dockGap) xNew = parentForm.Location.X;
                isDocked = true;
            }
            if ((bottomGap <= dockGap) && (((thisForm.Location.X >= xRange[0]) && (thisForm.Location.X <= xRange[1])) ||
                ((thisForm.Location.X + parentForm.Width >= xRange[0]) && (thisForm.Location.X + parentForm.Width <= xRange[1]))))
            {
                yNew = parentForm.Location.Y + parentForm.Height;
                if (xGap <= dockGap) xNew = parentForm.Location.X;
                isDocked = true;
            }
            return new Point(xNew, yNew);
        }

서브폼에서 좀 복잡해 보이긴 하지만, 메인폼의 영역을 알아와, 현재 서브폼의 위치가 달라 붙어야하는 위치라면 붙여 주고, 메인폼에 그 사실을 알리는 역할을 합니다.
이 소스를 응용하면, 상용프로그램처럼 윈도우간 도킹 기능을 만드는 것도 어렵지 않을 것 같습니다.

ps. 좌표를 일일이 비교해가며 구현하는 방법 외에 Class Library나 다른 방법을 아시는 분은 꼭 피드백을 부탁 드립니다!!
Posted by 맨날맑음
,