msdn 라우팅된 이벤트 링크

링크 정리

  • 라우트된 이벤트란 이벤트를 발생시킨 특정 개체뿐 아니라 요소 트리의 여러 수신기에서 처리기를 호출할수 있는 이벤트 형식
  • RoutedEvent클래스의 지원을 받고 WPF 이벤트 시스템에 의해 처리 되는 CLR이벤트이다.
  • 라우팅 전략

    -버블링 - 이벤트 소스의 이벤트처리기가 호출됩니다. 그런 다음 라우트된 이벤트가 요소 트리 루트에 도달할때까지 이후 부모 요소에 라우팅합니다. 대부분의 라우트된 에븐트는 버블링 라우팅 전략을 사용합니다. 버블링 라우트된 이벤트는 일반적으로 특정 콘트롤 또는 기타 UI 요소의 입력 또는 상태 변경을 보고 하는데 사용됩니다.

    -직접 - 이벤트에 응답하여 처리기를 호출할수 있는 기회가 소스 요소 자체에만 부여됩니다.

    -터널링 - 처음에는 요소 트리 루트의 이벤트 처리기가 호출됩니다. 그런 다음 라우트된 이븐테가 경로의 이후 자식 요소를 따라 라우트된 이엡느 소스인 노드 요소를 향해 라우팅합니다. 터널링 라우팅된 이벤트는 일반적으로 콘트롤 합성에 사용하거나 그 일부로 처리되어 합성 파트에 온 이벤트는 의도적으로 표시하지 않거나 전체 콘트롤에 고유한 이벤트로 대체됩니다.터널링 라우트된 이벤트 이름에는 "Preview"라는 접두사가 붙는다.

     

  • 어떤 엘리먼트들이 트리 관계를 이룰때 발생된 이벤트는 트리를 구성하는 부모 또는 자식에게 전달될 필요가 있고 트리를 따라서 이벤트가 전달되는 과정을 이벤트 라우팅이라고 하고 이렇게 트리를 따라 마치 chain과 같이 전달된 이벤트를 RoutedEvent라 한다 - 공도소프트  엘리먼트 트리에서 이벤트를 전달하는 방법에는 발생된 이벤트를 이벤트가 발생된 엘리먼트부터 최상위 엘리먼트까지 위쪽 방향으로 올려주는 버블링(Bubbling) 과 발생된 이벤트를 이벤트가 발생된 엘리먼트를 포함하는 최상위 엘리먼트에서 이벤트가 발생된 엘리먼트까지 아래 방향으로 내려주는 것을 터널링(Tunneling)이다.(Flex에선 Capturing)

 

 

클래스들은 여러개의 이벤트를 노출하며 각각의 이벤트는 여러개의 이벤트 구독자를 가질수 있다.

ex

<Button PreviewMouseDown="PreviewMouseDonwButton" MouseDown="MouseDonwButton"/>

직접 전달 이벤트는 보통의 .Net 이벤트처럼 동작한다. 이는 이벤트를 발생시킨 요소에 등록된 이벤트 핸들러만 이벤트가 통지되며 어떤 라우팅도 발생하지 않음을 의미한다.

 

WPF는 직접 전달 이벤트를 제외한 대부분의 라우팅된 이벤트를 버블링과 터널링의 쌍으로 구성한다. 터널링 이벤트 이름은 항상 Preview 로 시작하며 우선적으로 발생한다.

 

 

이벤트 라우팅 중단하기

만일 Preview 이벤트에 Handled 속성을 지정하면 Preview 이벤트의 터널링이 중단될뿐만 아니라 그에 해당하는 버블링 이벤트의 발생도 중단된다.

 ex.

void ButtonDownCanvas(object sender, RoutedEventArgs e)

{

e.handled = true;

}

 

이벤트 핸들러 등록하기

ex 4-4 myEllipse.MouseDown += MouseDownEllipse;

     myEllipse.PreviewMouseDown += PreviewMouseDownEllipse;

라우팅된 이벤트 시스템의 이벤트 핸들러 등록

ex 4-5 myEllipse.AddHandler(Ellipse.MouseDonwEvent, new MouseButtonEventHandler(MouseDownEllipse));

     myEllipse.AddHandler(Ellipse.PreviewMouseDownEvent, new MouseButtonEventHandler(PreviewMouseDownEllipse));

두개의 코드(4-4, 4-5)는 동일한 의미이다.

코드 비하인드 파일은 통상적으로 이벤트 핸들러를 등록하기 위한 최적의 위치다.

디자이너가 XAML파일의 요소들에 이름을 할당하고 개발자는 코드 비하인드 파일에서 이벤트 핸들러를 등록하는 것이다.

 

결합이벤트(attached event)

결합이벤트는 결합속성과 동일한 방법으로 라우팅된 이벤트를 사용하는 것이다. 결합이벤트를 처리하는 코드는 약간 다르다. 일반적인 CLR 이벤트들은 결합이벤트 개념을

지원하지 않으므로 ex4-4에서 사용했던 원래의 C# 이벤트 문접은 사용할수 없다. 대신 결합 이벤트를 표현하는 RoutedEvent 객체를 전달하는 AddHandler 메소드를 호출해야 한다.

4-5 처럼 써야 한다. 명시적 결합이벤트 처리.

도우미 함수를 이용한 결합 이벤트처리

ex

Mouse.AddPreviewMouseDownHandler(myEllipse, PreviewMouseDOwnEllipse);

Mouse.AddMouseDownHandler(myEllipse, MouseEllipse);

AddHandler 메소드는 모든 종류의 이벤트에 대한 핸들러를 등록할수 있기 때문에 두번째 매개 변수는 Delegate타입이어야 한다. 결합 이벤트를 정의하는 클래스들은 일반적으로

그에 대응하는 도우미 메소드도 함께 제공하여 개발자의 편의를 제공해야 하는 것으로 약속되어 있다.

 


마우스 입력

기본적인 마우스 이벤트에는 Click 이벤트가 포함되지 않는다. 이는 클릭 이벤트가 기본적인 마우스 입력보다 더 높은 수준의 개념이기때문이다. 게다가 클릭이벤트는 하나의 마우스 이벤트에 직접 대응할 필요가 없다. 일반적으로 사용자는 클릭 이벤트가 등록된 컨트롤 위에 마우스가 위치해 있는 동안 마우스 버튼을 눌렀다 뗀다. 따라서 이 고수준의 이벤트는 좀 더 특별한 요소 타입에 의해 제공된다. Control 클래스는 PreviewMouseDoubleClick과 MouseDoubleClick 이벤트 쌍을 정의하고 있다.

 

마우스 입력과 히트 테스트

WPF는 마우스 입력을 처리할때 항상 요소의 모양을 고려한다. 많은 그래픽 시스템은 히트 테스트(hit test)를 수행할때 사각형을 기준으로 처리한다. WPF는 히트테스트의 대상이 되는 사용자

인터페이스 요소의 모양이 단순하든 아니든 복잡하던 이처럼 단순하게 히트테스트를 처리 하지 않는다.

도넛 모양의 버튼이 있을때 빈공간을 클릭했을때 다른 콘트롤의 이벤트를 발생시키고 싶지 않을때 투명브러쉬로 가운데를 칠한다. 그러면 가운데 공간에 클릭하더라도 도넛모냥의 버튼이 이벤트 대상이 된다.

반면에 사람눈에는 보이지만 마우스 이벤트를 받지 않으려면(마우스에게 보이지 않는 상태) IsHotTestVisible속성을 false 로 지정한다. 지정하면 도넛 버튼 아래 있는 요소가 이벤트를 발생하게 된다.

 

마우스 상태

Mouse 클래스는 마우스의 상태를 확인하거나 변경하기 위한  static 속성과 메소드를 제공한다.

ex. Mouse.GetPosition(요소) - 현재의 마우스 위치를 알아낼수 있다.

마우스 캡쳐 - 마우스 위치에 관계없이 모든 마우스 입력 이벤트가 마우스를 캡쳐하는 요소에 전달되는 것

 

키보드 입력

마우스 입력의 대상은 항상 마우스 아래에 위치한 요소이거나 현재 마우스를 캡처하는 요소다.  키보드 입력은 다르다.

WINDOWS는 직접적인 키보드 입력을 위한 다른 매커니즘이 있다. 어떤 한 순간에는 툭정 교소만이 포커스(focus)를 갖도록 돼있다. 포커스를 갖는다는 것은 해당 요소가 키보드 입력에 대한 대상으로 동작하고 있음을 의미한다. UIElement 기반 클래스는 IsFocused 속성을 가지고 있어 원칙적으로 모든 요소는 포커스를 가질수 있다. 이 속성을 false 하면 포커스를 가질수 없다.

WPF는 논리적 포커스와 키보드 포커스를 구분한다. 키보드 포커스는 오직 하나의 요소에만 주어진다.  그리고 키보드 입력을 받은 요소를 가리키고 논리적 포커스는 포커스 범위내에서 포커스가 있는 요소를 가르킨다.

 

키보드 상태

Keyboard클래스는 Modifiers라는 이름의 static 속성을 제공한다. 이 속성으로 언제든지 alt, shift, ctrl 눌려있는지 알수 있다.

ex ctrol 키 사용여부

if(Kyeboard.Modifiers & ModifierKeys.Control) != 0)

{

isCopy = true;

}

ex 개별적인 키(Home) 상태알아내기

bool homeKeyPressed = Keyboard.IsKeyDown(Key.Home);

 

커맨드처리

커맨드(command)란 응용프로그램이 사용자의 요청에 의해 수행하는 동작이다. 커맨드는 사용자 입력을 추상화 한 것으로 키보드로 ctrl+C를 눌렀든 메뉴에서 [편집][복사]를 선택했건

도구 막대에서 [복사]를 눌럿던 동일한 커맨드의 다른 표현으로 취급할수 있께 해준다. 커맨드 시스템을 통해 UI요소가 커맨드에 대한 하나의 이벤트 핸들러만을 제공함으로써 코드의 복잡성을 감소시키고 깔끔하게 유지할수 있다. 게다가 UI 요소들에 대한 선언적인 스타일의 이벤트 처리도 가능하다.

ex.

<DockPanel>

<Menu DOckPanel.Dock="Top">

<MenuItem Header="_Edit">

<MenuItem Header="Cu_t" Command="ApplicationCommands.Cut"/>

<MenuItem Header=" _Copy" Command="ApplicationCommands.Copy" />

<MenuItem Header="_Paste" Commands="ApplicationCommands.Paste" />

</MenuItem>

</Menu>

<ToolBatTray DockPanel.Dock="Top">

<ToolBar>

<Button Command="Cut" Content="Cut" />

<Button Command="Copy" Content="Copy" />

<Button Command="Paste" Content="Paste" />

</ToolBar>

</ToolBarTray>

</DockPanel>

각각 메뉴 아이템은 커맨드와 연결되어 있다. 이것이 텍스트 상자에서 클립보드 작업을 수행하기 위해 필요한 전부다. 내장된 잘라내기, 복사 및 붙여넣기 커맨드들은 표준 키보드 단축키와 자동적으로 연동된다.

 

커맨들 시스템의 5인방

커맨드 객체(Command object) - 복사나 붙여넣기 등 특정 커맨드를 표현하는 객체

입력 바인딩(Input binding) - 특정 입력(ex. ctrl+C)과 커맨드(ex 복사)사이의 연동을 의미한다.

커맨드 원본(Command source) - Button과 같이 커맨드를 수행하는 객체 또는 입력 바인딩을 의미한다

커맨드 대상(Command target) - 커맨드를 수행하도록 요청을 받은 UI요소-대체로 커맨드가 실행될때 키보드 포커스를 가진 콘트롤을 의미한다

커맨드 바인딩(Command binding) - 특정 커맨드를 처리하는 방법을 알고 있는 특정 UI요소를 선언하는 것

 

커맨드객체

커맨드 정의하기

커맨드 객체는 커맨드를 어떻게 처리해야 하는지는 알지 못하며 이는 커맨드 바인딩이 수행해야 할 작업이다. 커맨드 객체는 대체로 ApplicationCommands.Properties와 같이 static 속성을 통해 접근할수 있도록 만들어진다.

ex. 사용자 정의 커맨드를 사용하는 예제

//...

using System.Windows.Input;

//...

 public static RoutedUICOmmand AddToBasketCommand;

 

static MyAppCommands()

{

InputGestureCollection addToBasketImputs = new InputGestureCollection();

addToBasketInputs.Add(new KeyGesture(Key.B, ModifierKeys.Control | ModifierKeys.Shift));

AddToBasketCommand = new RoutedUICommand("Add to basket", "AddToBasket", typeof(MyAppCommands), addToBasketInputs);

}

RoutedUICommand의 첫번째 인자는 사용자 인터페이스에 나타나야 할 이름이다. 두번째 인자는 내부적으로 코드에서 사용될 커맨드 이름이다. 이 이름은 커맨드가 저장될 필드의 이름에서

command 접미사를 제외한 이름과 정확히 일치해야 한다. 내장 커맨드를 사용하는 경우 응용프로그램 커맨드는 자신에게 정의된 어떤 것도 실행하지 않는다. 단지 식별자일뿐이다.

XAML코드에서 커맨드 활용하기

ex

<Button Command="Copy">COPY</Button>

커맨드 Copy는 ApplicationCommands.Copy의 약칭이다.

 

커맨드가 MyNameSpace.MyAppCommands 클래스에 정의되어 있고 MyLib 컴포넌트내에 구현되어 있고 AddToBaskteCommand 이름의 필드에 보관되어있다면

ex

<Window xmlns:m="clr-namespace:MyNamespace; assembly=MyuLib" ...>

//...

<Button Command="m:MyAddCommands.AddToBasketCommand>Add to basket(/Button>

//...

 

입력바인딩

키보드 단칙크와 같은 특정 형태의 입력 행동양식과 커맨드를 연동시키는 것이다. 대부분의 내장 커맨드들은 표준 행동양식과 연동되어 있다. 예를 들어 Application.Commands.Copy

커맨드는 복사를 수행하는(ctrl+c) 표준키보드와 연동되어 있다.

 

커맨드 원본 - 커맨드를 실행하기 위해 사용되는 객체며, 버튼이나 하이퍼 링크 또는 메뉴 아이템과 같은 사용자 인터페이스요소가 사용될 것이다.

커맨드 원본은 모두 ICommandSource 인터페이스를 구현하고 있다.

CommandParameter 속성을 이용하면 커맨들가 실행될때 추가 정보를 전달할수 있다. 이때 파라미터 값은 하드코딩하는 것보단

필요로 하는 데이터를 지정한다면 커맨드 핸들러는 아래 예제에서 보듯 커맨드 대상의 DataContect 속성을 조회할수 있다

ex

void AddToBasketHandler(object sender, ExecuteRoutedEventArgs s)

{

FrameworkElement source = (FrameworkElement) e.Source;

ProduectInfo product = (ProductInfo) source.DataContext;

//...

}

 

커맨드 바인딩

언제든지 커맨드를 사용하려면 무언가가 커맨드의 실행에 반드시 대응해야 한다.

CommandBinding 클래스 객체는 특정 상요자 인터페이스 요소의 범위 내에서 특정 커맨드 객체와 핸들러 함수를 연결시킨다.

이 CommandBinding 객체는 커맨드가 UI를 통해 터널링 또는 버블링될때 발생하는 PreviewExecute와 Execute 이벤트를 제공한다.

커맨드 바인딩은 UIElement 요소에 정의된 CommandBindings  컬렉션 속성에 보관된다.

ex. ApplicationCommands.New 커맨드를 바하인드 코드에서 처리하는 방법.

public partial class Window1 : Window

{

InitializeComponent();

CommandBinding cmdBindingNew = new CommandBinding(ApplicationCommands.New);

cmdBindingNew.Executed += NewCommandHandler;

CommandBnidings.Add(cmdBindingNew);

}

 

void NewCommandHandler( ....)

{

//...

}

 

커맨드의 활성화 및 비활성화

CommandBinding 객체는 커맨드의 실행을 지원하는 것은 물론 특정 커맨드가 현재 사용가능한지 여부를 결정할때도 사용할수 있다. 터널링에 사용되는

PreviewCanExecute와 버블링에 사용되는 CanExecute 이벤트쌍을 제공한다.

public Winodw1(){

CommandBinding redoCommand = new CommandBinding(ApplicationCommands.Redo);

redoCommandBinding.CanExecute += RedoCommandCanExecute;

CommandBindings.Add(redoCommandsBinding);

}

 

void RedoCommandCanExecute(object sender, CanExecuteRoutedEventArgs s)

{

e.CanExecute = myCustomUndoManager.CanRedo;

}

커맨드 바인딩은 커맨드 라우팅 중 버블링에 의존하는 기능이다. 이벤트 버블링 덕분에 커맨드를 단 한곳에서 손쉽게 처리할수 있다. 대부분의 커맨드 라우팅은 매우 직관적이다.

일반적으로 커맨드 라우팅의 대상은 키보드 포커스를 가진 요소이며, 다른 이벤트들과 달리 터널링과 버블링을 사용한다.

 

커맨드 라우팅

모든 내장 커맨드 객체는 RoutedUICommand 라는 클래스를 사용하며 응용 프로그램에 특화된 커맨드를 정의하려면 일반적으로 이 클래스를 이용한다. RoutedUIElement 클래스는 커맨드가 실행될때 올바른 커맨드 바인딩을 찾아내는 메커니즘을 제공한다. 커맨드 바인딩을 찾는 케머니즘은 때떄로 콘텍스트에 의해 결정된다.

RoutedUICommand 클래스는 포서크 범위(focus scope)에 의존한다. RoutedUIComponent 클래스는 커맨드 바인딩이 실패하면 최초의 커맨드 대상이 포커스 범위에 속하는지를 검사한다. 만일 커맨드 대상이 포커스 범위에 속해 있다면 WPF는 포커스 범위의 부모(대체로 윈도우)를 찾은 후 커맨드 대상을 다시 지정하고 부모 포커스 범위에서 놀리적인 포커스를 가진 요소, 즉 마지막으로 포커스를 가지고 있던 요소를 선택한다. 이렇게 되면 두번째 터널링과 버블링 과정이 발생한다. 결국 커맨드 대상은 메뉴가 열리기 전 또는 도구 막개의 버튼이 클릭되기 전에 포커스를

가지고 있던 요소가 된다.

 

 

코드 기반 입력 처리와 트리거 기반 입력처리의 비교ㅣ

만일 사용자 입력을 처리하는 이유가 단순히 사용자에게 가시적인 피드백을 제공하기 위한 것이라면 이벤트 핸들러나 사용자 정의 커맨드를 작성하는 것은 지나치다!!

단지 가시적인 피드백을 위한 것이라면 사용자 인터페이스 마크업에서 트리고(trigger)를 이용하기만 해도 가능하다.

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 9. 23. 19:25

 

데이터바인딩을 사용하지 않는 경우

UI가 객체의 변화를 추적하는 안정적인 방법은 객체가 속성값이 변경될때마다 이벤트를 발생시키는 것이다.

INotifyPropertyChanged 인터페이스를 구현해야 한다

 

ex.

참조추가 - using System.ComponentModel;

상속 추가 - INotifyPropertyChanged

핸들러 등록

 public event PropertyChangedEventHandler PropertyChanged;

 

이벤트 발생 함수 선언
        protected void Notify(string propName)
        {
            if (this.PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }

 

property의 set함수를 값의 변경 확인후에 이벤트를 발생시키도록 코드 수정

ex

string name;

        public string Name
        {

            get { return this.name; }
            set {
                if (this.name == value) { return; }
                this.name = value;
                Notify("Name");

            }
        }

값변화를 확인해야하는 객체에 이벤트핸들러 추가하기

 person.PropertyChanged += person_PropertyChanged;

그의 이벤트 핸들러.

void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch(e.PropertyName)
            {
                case "Name":
                    this.nameTextBox.Text = person.Name;
                    break;
                case "Age":
                    this.ageTextBox.Text = person.Age.ToString();
                    break;
            }
        }

여기서 이벤트의 이름은 Notify()에서와 동일해야 하며 대소문자를 구분한다.

 

위 예제의 개략적인 진행상황은 다음과 같다

  1. 사용자가 버튼을 클릭하면 click 이벤트가 발생한다
  2. Click 이벤트 핸들러가 Person 객체의 나이를 가져온다
  3. Click 이벤트 핸들러에서 Person 객체의 나이를 증가시킨다.
  4. Person 의 Age 속성에서 PropertyChanged 이벤트를 발생시킨다
  5. PropertyChanged 이벤트가 UI코드내의 이벤트 핸들러로 라우팅된다.
  6. UI 코드가 나이를 표시하는 TextBox 콘트롤의 값을 11에서 12로 변경한다
  7. Button 클릭 이벤트 핸들러가 새로운 나이를 적용한 메시지 박스를 보여준다.

 

 

 

데이터바인딩

 

WPF에서의 데이터 바인딩은 데이터 바인딩 엔진에 두 속성을 등록하고 엔진이 동기화를 담당하도록 필요한 경우 적당한 타입 변환을 수행하도록 하는 작업을 의미한다.

데이터 바인딩 엔진에 동기화를 관리할 두개의 속성을 등록하려면 Binding 객체를 이용하면 된다.

바인딩이 되는 객체(binding source)와 바인딩할 요소의(binding target) 의존속성을 동기화와 타입변환을 통해서 연결한다.

객체는 데이터 원본이고 요소는 UI 컴포넌트인 듯.

ex.1

<Text Box ...>

<TextBox.Text>

<Binding Path="Age"/>

</TextBox.Text>

</TextBox>

객체는age이고 요소는 TextBoxt가 된다. 그리고 바인딩할수 있는 대상은 TextBox의 의존속성뿐이다.

ex.2

<TextBox Text="{Binding Path=Age}"/> || <TextBox Text="{Binding Age}"/>

동일한 의미이다. Path를 생략할수 있기때문에.

ex3. 다양한 기능의 바인딩 예제

 <TextBox>
            <TextBox.Foreground>
                <Binding Path="Age" Mode="OneWay" Source="{StaticResource Jack}"

                             Converter="{StacticResource ageConverter}"/>
            </TextBox.Foreground>

        </TextBox>   

여러 바인딩 특성을 하나로 묶으려면 이름과 값의 쌍을 콤마로 구분하여 나열하게 되며, 공백이나 줄바꿈도 사용할수 있다.

 

바인딩에서 TextBox 콘트롤은 바인딩 대상이고 데이터를 제공하는 객체인 바인딩 원본의 변경사항을 감시하는 역할을 담당한다. 모든 WPF 요소들은 바인딩 대상이 될수 있지만

해당 요소의 의존속성만 바인당할수 있다. 바인딩 원본 객체의 경우에는 모든 CLR 속성과 의존속성을 바인딩할수 있다.-ADO.NET 데이터원본도 가능하다 -

일반적으로 바인딩 될 원본 데이터는 데이터 콘텍스트에서 가져온다.

 

암시적 데이터원본

다른 특별한 명령이 없는 경우 바인딩은 데이터원본을 찾기 위해 data context를 탐색한다. WPF의 모든  FrameworkElement객체와 FrameworkContentElement 객체는 data context

속성을 가지고 있다. DataContext 속성은 object 타입을 사용하기떄문에 모든 객체를 사용할수 있다. 바인딩객체는 바인딩 원본으로 사용되는 객체를 탐색할때 자신이 정의된 위치부터

객체 트리를 탐색하여 DataContext 속성이 지정된 객체를 찾는다.

 

ex.<Window ...>

<Grid Name = "grid">

//...

<TextBlock ..>Name</TextBlock>

<TextBox Text="{Binding Path=Name}"/>

<TextBlock.... >Age </TextBlock>

<TextBox Text="{Binding Path=Age}"/>

</Grid>

</Window>

DataContext 탐색순서

  1. 처음 자신이 속한 TextBox의 DataContext 속성을 탐색한다
  2. TextBox가 속한 Grid의 DataContext 속성을 탐색한다
  3. TextBox가 속한 Grid가 속한 Window의 DataContext 속성을 탐색한다

 

INotifyPropertyChanged 인터페이스를 구현하고 바인딩을 걸면 된다.  이 인터페이스가 구현되어 있어야 객체의 변경사항을 감시할수 있다.

그리고 속한 객체의 DataContext에 바인딩 소스를 걸어주면 된다.

바인딩을 하면 이벤트핸들러처리, UI 초기화 등을 구현할 필요가 없다!!!

 

data island - MS IE에서 디스플레이 되는 HTML 페이지내에 존재하는 XML Data Source Object(DSO)이다. XML DSO는 InternetExplorer Version4에서 구현된 ActiveX콘트롤이다. XML DSO는 외부의 XML 파일에서 컨텐츠를 추출하여 HTML페이지로 추출하는 방식을 제공한다.

ex XAML 코드에서 사용자 정의 타입의 인스턴스 생성하기

<Window x:Class="WPF_EX_03.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WPF_EX_03"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <local:Person x:Key="John" Name="동현" Age="21"/>
       
    </Window.Resources>

//...

</Window>

ex.XAML에서 선언된 객체를 이용한 데이터 바인딩

//..

 <Canvas Name="canvas" DataContext="{StaticResource John}"> //john은 리소스의 키값. 키값은 대소문자 구분함.

//...

ex XAML에서 바인딩된 객체 사용하기

  Person person = (Person)this.FindResource("John");// resource에서 찾은 다음에 형변환.
            ++person.Age;

 

값의 변환

바인딩 소스 속성은 Int32이고 Foreground 속성은 Brush 타입이라면 그 사이에 적절한 형 변환이 있어야 한다. 값변환기는 ConvertConvertBack 두개의 메소드를 정의하는

IValueConverter 인터페이스를 구현한 객체다. Convert 메소드는 원본 데이터를 대상 U 요소의 데이터로 변활할때(예제에서는 Int32를 Brush타입으로 변환)호출된다.

ConvertBack 메소드는 UI의 데이터를 원본 데이터로 변환할때 호출된다.

ex.xaml.cs

[ValueConversion(/*sourceType*/ typeof(int), /*targetType*/ typeof(Brush))]
    public class AgeToForegroundConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo cultureinfo)
        {
            if (targetType != typeof(Brush))
            {
                return null;
            }
            int age = int.Parse(value.ToString());
            return (age > 25 ? Brushes.Red : Brushes.Blue);

        }
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo cultureinfo)
        {
            throw new NotImplementedException();
        }

    }

 

.xaml 값변환기를 이용한 바인딩

 <Window.Resources>
        <local:Person x:Key="John" Name="동현" Age="21"/>
        <local:AgeToForegroundConverter x:Key="ageConverter"/>
    </Window.Resources>
    <Canvas Name="canvas" DataContext="{StaticResource John}">
        <TextBlock Width="68" Height="20" Canvas.Left="10" Canvas.Top="17">Name : </TextBlock>
        <TextBox Text="{Binding Path=Name}" Height="19.78" Canvas.Left="76" Canvas.Top="14" Width="162"></TextBox>
        <TextBlock Height="14" Canvas.Left="10" Canvas.Top="43" Width="29.827">Age : </TextBlock>
        <TextBox Text="{Binding Path=Age}" Foreground="{Binding Path=Age,Converter={StaticResource ageConverter}}"  Name="ageTextBox"
                 Height="19.78" Canvas.Left="76" Canvas.Top="40" Width="162"></TextBox>
        <Button Name="birthdayBtn" Foreground="{Binding Path=Foreground, ElementName=ageTextBox}"
                Height="22" Canvas.Left="76" Canvas.Top="72" Width="162">생일</Button>
    </Canvas>

 

일반적인 경우 값변환기를 구현할필요가 없다 .Net1.0부터 지원하는 타입변환기를 사용할수 있기때문이다. 이런 식의 값변환기는 특정한 상황을 가지는 응용프로그램에서 유용하다.

 

유효성검사

유효성 검사코드(validation code)는 실제로는 System.Windows.Control.ValidationRule 클래스로부터 파생되는 클래스의 인스턴스이며, Validate 메소드를 오버라이딩하여 구현할수 있다.

ex. .xaml

<TextBox>

<TextBox.Text>

<Binding Path="Age" NotifyOnValidationError="True">

<Binding.ValidationRules>

<ExceptionValidationRule/>

</Binding.ValidationRule>

</Binding>

</TextBox.Text>

</TextBox>

 

.xaml.cs

 Validation.AddErrorHandler(this.ageTextBox, ageTextBox_ValidationError);   //애러이벤트 핸들러 등록
           
     
        void ageTextBox_ValidationError(object sender, ValidationErrorEventArgs e)
        {
            int age = 20;
            this.ageTextBox.Text = age.ToString();
            MessageBox.Show((string)e.Error.ErrorContent, "유효성 검사님 오류");
        }

 

사용자 정의 유효성 검사 규칙.

ValidationRule 클래스를 상속한 후 Validate메소드를 오버라이드해서 사용한다.

ex

 public class NumberRangeRule : ValidationRule
    {
        int min;
        public int Min
        {
            get { return min; }
            set { min = value; }
        }

        int max;
        public int Max
        {
            get { return max; }
            set { max = value; }
        }

        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            int number;
            if (!int.TryParse((string)value, out number))
            {
                return new ValidationResult(false, "잘못된 숫자입니다");
            }

            if (!(number >= min && number <= max))
            {
                return new ValidationResult(false, string.Format("유효한 범위는 {0}-{1}입니다", min, max));
            }

            return ValidationResult.ValidResult;
        }

    }

 <TextBox.Text>
                <Binding Path="Age" NotifyOnValidationError="True">
                    <Binding.ValidationRules>
                        <local:NumberRangeRule Min="0" Max="25"/>
                    </Binding.ValidationRules>                   
                </Binding>
            </TextBox.Text>

 

요약

데이터 바인딩은 두 값 사이의 동기화를 유지하는 것이다. 두 값 중 하나인 바인딩 대상은 의존속성이며 대체로 UI요소에서제공된다. 다른 하나는 바인딩 원본으로 CLR속성이며

XPath표현식의 결과이거나 의존속성 혹은 ADO.NET 이 제공하는 것처럼 런타임 이전에 어떤 데이터를 가지게 되는지 알수 없는 동적 속성들이 사용될수 있다.

기본적으로 대상이나 원본이 변경되면 다른 값도 업데이트되지만 여러 바인딩 모드를 이용하여(ex. One-Way, One-TIme) 이 동작을 제어할수 있다.

데이터가 변뎡될 때 변환기를 사용할수 있다면 타입변환이 발생하며 여러분이 변환이나 유효성 검사 과정에 대한 전반적인 제어가 가능해서 어떤 방법을 지정했다 하더라도 데이터의

범위를 제한하고 데이터 형식을 변환하거나 풍선도움말에 오류를 자동으로 보여줄수 있다.

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 9. 18. 14:42

XAML - .Net 객체를 생성하고 초기화하기 위한 XML기반의 언어이다. 비록 WPF에 구현된것보다는 CLR에서 지원하는 다양한 타입들을 위해 사용되기는 하지만 XAML은 WPF에서 사람이

UI를 기술하기 위한 방법으로 사용된다. XAML은 가능한 한 .NET과 직접 매팅할수 있도록 만들어졌다.  XAML의 요소는 .NET클래스의 이름이며, XAML의 특성은 클랫의 숙성이나 이벤트

의 이름이다.

Data Binding - 객체의 속성과 객체의 컬렉션이 하나 이상의 컨트롤의 데이터 뷰와 동기화되도록 유지하는 것이다. 데이터 바인딩의 목적은 객체 내의 데티터 변경이 일어났을때 컨트롤에 이를 반영하거나 반대로 컨트롤에 표시된 데이터를 사용자가 수정했을때 데이터를 업데이트하는 코드를 작서앟는데 필요한 시간을 감소시키는 것이다. 데티터와 컨트롤 사이의 동기화는

INotifyPropertyChanged 인터페이스와 INotifyCollectionChanged 인터페이스에 달려 있으며, 우리는 이들을 데이터와 데이터 컬렉션을 구현하는데 있어 매우 조심스럽게 사용해야 한다.

객체의 소비자에게 속성이 변경되었음을 알리기 위한 표준 인터페이스(standard interface)가 존재하는 것과 마찬가지로 소비가 클래스가 변경된 속성을 수집하기 위한 표준

인터페이스인 INotifyCollectionAhnged 인터페이스도 제공된다. WPF는 이 언터페이스를 구현하는 ObservableCollection이라는 클래스를 제공하며,

우리는 이 클래스를 이요하여 NickName객체가 추가되거나 제거될때 관련 이벤트가 발생하도록 할 것이다.

 

data binding 한 결과물을 ListBox에서 보여준다고 했을 경우에 뭔가 이상한 리스트가 만들어질 것이다.

이것은  ListBox 콘트롤이 해당하는 객체를 모두 보여주려고 하기때문이다. 우리가 원하는 항목을 원하는 것만 보여주기 위해서는

다음 처럼 개별적인 datatemplate을 지정해주어야 한다

ex

<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" Height="180">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock>
                    <TextBlock Text="{Binding Path=Name}"></TextBlock>
                    <TextBlock Text="{Binding Path=Nick}"></TextBlock>              
                    </TextBlock>                   
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

 

의존속성 - 참고 포스트

대상요소에 데이터 바인딩을 지원하기 위해서 더 필요한 것은 TextBlock요소는 표준속성 래퍼(property wrapper)를 통해 TextContent 속성을 노출하고 있지만 TextBlock 요소를 데이터 바인딩,스타일링, 그리고 애니메이션과 같은 WPF 서비스들과 통합하기 위해서는 의존속성(dependancyProperty)이 필요하다. 의존속성은 컨테이너 요소로부터 값을 상속하는 기능, 객체, 독립적 저장소(잠재먹으로 커다란 메모리 절약효과를 제공해준다), 값의 변경을 추적하는 기능등 .Net 속성이 제공하지 못하는 여러가지 기능을 제공한다.

 

리소스 - 명명된 일련의 데이터로, 코드와는 별도로 분리되어 응용프로그램이나 컴포넌트와 함꼐 제공된다. .Net은 리소스에 대한 풍부한 지원을 제공한다.

 

컨트롤템플릿 - 컨트롤의 속성을 조작하여 외관을 바꿀수 있고 컨트롤의 template속성을 이요하여 컨트롤의 외관을 완전히 다르게 대체할수 있다.

ex

<Button DockPanel.Dock="Bottom" x:Name="addButton" Content="추가">
            <Button.Template>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Grid>
                        <Ellipse Width="128" Height="32" Fill="Yellow" Stroke="Black"></Ellipse>
                        <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center">
                        </ContentPresenter>                       
                    </Grid>
                </ControlTemplate>
            </Button.Template>
        </Button>

버튼의 모양과 색을 바꾼 예제

 

리소스

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 9. 18. 09:33

닷넷프로그래밍정복 김상형저 가메출판사

 

닷넷은 데이터베이스 클라이언트 제작을 훌륭히 지원한다. 응용프로그램 작성을 위한 풍북하고 강력한 언어적 지원은 물론이고 DB접속 라이브러리까지 포함되어 있어 별도의 외부 라이브러리 없이도 자체적인 개발이 가능하다. 개발 툴의 지원도 막강하여 마법사와 각종 코드 생성 툴을 통해 최소한의 의사표현만으로도 자동화된 처리가 가능하다. 심지어 DB 성성이나 테이블 관리 등 설계와 테스트를 위한 서버의 기능까지도 일부 포함하고 있어 그야말로 DB응용프로그램제작을 위한 최적화된 툴이다.

 

 

 

특징

ADO(ActiveX Data Objects)는 MS가 COM프로토콜 기반ㅇ에 메일이나 파일 시스템 등 비 관계형 데이터에도 접속할수 있는 OLE DB를 사용하기 위해 래핑한 것이다.

ADO는 서버와의 연결을 항상 유지한태로 동작했는데 이런 방식을 연결형이라고 한다. 연결형은 서버에 연결된 상태에서 필요한 SQL문을 실행하거나 저장 프로시서를 직접적으로 호출하는 방식이다. 클라이언트는 사용자의 요청만 처리하며 데이터 관리는 서버에게 전달하여 서버가 모든 것을 처리한다. 클라리언트는 데이터 처리를 요청하고 서버는 요청에 응답한다.

ADO 장점

모든 데이터를 서버가 관리함으로써 클라이언트의 메모리 사용량이 적고 별다른 준비를 할 필요가 없으므로 기동속도가 빠르다. 필요할때만 명령을 보내므로 오버헤드가 적은 편이다. 서버가 할수 있는 모든 요청을 다 보낼수 있는 섬세한 데이타 조작이 가능하며 심지어 새로운 DB개체를 새애성하는 DDL명령까지도 수행가능하다. 항상 연결된 상태에서 서버의 실시간 정ㅂ조를 참조하므로 현재성도 높다.

ADO 단점

다수의 클라이언트가 동시에 요청을 보내면 서버의 부담이 증가한다는 점이 가장 큰 문제점이다. 데이터를 출력할때마다 서버의 데이터를 매번 읽어야 하며 반복적인 작업을 할 경우 네트워크 트래픽이 과다해져 전체적인 속도가 떨어지고 반응성도 나빠진다. 다수의 동시 사용자를 무리 없이 지원하려면 서버의 하드웨어 사양이나 네트워크 장비가 엄청나게 고성능이어야 한다. 또 대부분의 DBMS는 연결되는 클라리언트 수에 따라 라이선스 비용을 매기므로 연결이 많아지면 전체적인 비용이 기하급수적으로 증가한다.

 

ADO.NET은 기존의 연결형 외에 서버와의 연결이 끊어진 채로 동작할수 있는 비연결형을 추가로 지원한다. 비연결형은 연결을 하기는 하되 필요한 데이터만 잽싸게 가져오고 연결을 끊어버리는 방식이다. 최초 연결시 서버의 데이터에 대한 사본을 작성하며 오프라인에서 사본을 조작한 후 결과를 서버로 보낼 때만 다시 연결한다. 여기서 비연결이란 아예 연결을 하지 않는 것이 아니라 꼭 필요할 때만 잠시 연결한다는 뜻이다. 서버의 데이터를 가져온 클라이언트가 혼자서 북치고 장구친다는 뜻이다. 서버는 데이터 저장 역할만 하면 되므로 무엇보다 서버에 부담이 적어서 훨씬 더 많은 동시 사용자를 지원할수 있다는 것이 장점이다.

 

비연결형의 핵심은 메모리에 구축된 DB인 DataSet 클래스이다. DataSet은 관계형 데이터베이스 하나에 대응되는 메모리 상의 오프라인 DB라고 할수 있는데 연결이 끊어진 동안 서버의 역할을 대신한다. 여러개의 테이블에 대해 동시에 작업할수 있고 다른 소스로부터 온 테이블도 같이 조작할수 있다. 이 기종 DBMS에서 가져온 데이터나 심지어 관계형이 아닌 데이터도 하나의 데이터 집합에 몰수 있다. 테이블들을 클래스화 하여 쉽게 사용할수 있을뿐 아니라 타입체크가 엄격해 오류 가능성도 낮다. 정적인 사본이므로 컨트롤에 바인딩하기도 편리한데 컨트롤과 연결해 놓으면 데이터 집합의 내용이 콘트롤에 나타나고 콘트롤을 편집하면 데이터 집합이 즉시 수정된다. 메모리에 완전한 사본을 가지고 있으므로 자유로운 랜덤 억세스가 가능하여 억세스 속도는 환상적이다. 반복적인 작업을 해도 속도 상의 불이익이 거의 없다.

단점은 대량의 데이터를 메모리에 계속 보유해야 하므로 클라이언트가 엄청난 리소스를 소모한다. DB의 용령은 적어도 10메가정도는 되며 클 경우 수백메가를  육박하기도 하는데 이 거대한 데이타의 크기만큼 메모리가 요구된다. 일단 가져오면 빠른속도로 억세스 할수 있지만 가져오는 시간이 오래 걸리므로 기동속도가 느리다. 서버에서 가져온 사본만을 조작하므로 새로운 개체를 생성하는 DDL명령은 수행할수 없으며 실시간으로 저사되는 정보가 아니므로 연결형에 비해 현재성도 떨어진다.

 

구조

ADO.NET은 DBMS에 독립적인 접속환경을 제공하는데 그 핵심은 공급자(Provider)이다. 공급자는 DB에 연결하고 명령을 실행하는 모듈로서 응용프로그램에게 DBMS의 데이터를 공급하는 역할을 한다. DBMS별로 공급자가 제공되며 응용프로그램은 공급자를 통해 DBMS에 연결하고 SQL명령을 실행한다. 공급자의 개념은 윈도우즈의 디바이스 드라이버와 개념이 유사하다. 그래픽 카드가 바뀌면 디바이스 드라이버만 바꾸면 될뿐 응용프로그램을 다시 컴파일할필요가 없다. 연결해보자면 각 DBMS는 그래픽카드가 될 것이고 ADO.NET은 장치관리자, 공급자는  드라이버에 해당한다.

공급자중에 가장 성능이 좋고 신뢰성이 높은 것은 SQL서버이며 닷넷과 가장 잘 어울린다.

 

 

테이블

마법사로 만든 코드들은 이런 맹점이 있다

  • 마법사로 코드를 만들기는 쉽지만 거꾸로 되돌리기는 어렵다. 직접 작성한 코드는 그냥 지우면 되지만 마법사가 만든 코드를 함부로 편집하면 잘 되던 것도 안된다
  • 익숙해지는데 많은 시간이 걸린다. 코드는 보고 따라하면 되고 잘 안될 경우 레퍼런스라는 친절한 안내자가 있지만 마법사는 조작순서를 일일이 외워야 하며 문서를 통해 참조하기 어렵다
  • 생각보다 버그가 많고 완성도가 떨어져 원하는대로 동작하지 않는 경우가 허다하다. 원하는 모든 동작을 마법사로 다 처리할수 없으며 결국은 손으로 직접마무리 해야 한다
  • 애쎄 배워봤자 버전이 바뀌면 왕창 달라져 지식으로서의 가치가 없다. 반면 코드는 하위호환성을 철저하게 유히되므로 애쌔 배울가치가 있다.

 

ADO.NET은 크게 연결긴을을 하는 부분과 조작기능을 하는 부분으로 나눌수 있다. 연결기능은 다음장에서 설명하고 이번장에서는 조작기능에 관해서만 한다. 조작가능을 제공하는 클래스는 메모리 내의 DB에 대해 사용하는 것이므로 공급자에 독립적이며 모든 공급자가 공통으로 사용한다.

 

[DB설치 및 실행 > 테이블 작성]

 

데이터베이스는 여러가지 개체들의 집합이되 주로 테이블로 구성되며 테이블끼리 관계를 이룬다. 테이블은 열과 제약조건으로 스키마를 구성하며 실제 정보에 해당하는 행들의 집합을 가진다. 관계형 DB의 가장 핵심은 테이블이며 닷넷에서 테이블은 DataTable 클래스로 표현된다. DB를 구성하는 가장 기본 단위가 테이블이다.

public DataTable([strinbg tableName, string tableNamspace])

두 개의 인수를 가지는데 둘 다 생략 가능하다. 첫번째 인수는 테이블 이름이되 생략가능하거나 빈문자열을 주면 Table1, Table2 따위의 기본 이름이 부여된다. 두번째 인수는 테이블의 네임스페이스는 지정하며 같은 이름의 다른 테이블을 구분하는 역앟을 하는데 잘 사용하지 않는다. 여러 데이터 소스로부터 테이블을 가져오다 보면 우연히 이름이 중복될수도 잇어 구분을 위한 목적으로 네임스페이스를 지정할수 있도록 되어 있다.

 

스키마

테이블의 열정보와 제약 정보는 테이블의 구조인 스키마를 결정한다. 사람에 대한 테이블이라면 이름, 주소, 전화번호따위의 열이 포함될 것이고 책에 대한 테이블이라면 제목, 저자, 출판사 따위의 열이 포함 될 것이다.

DataColumn - 테이블 하나에 복수 개의 열이 포함되므로 테이블은 Column 프로퍼티로 열의 컬렉션을 관리한다. Columns 프로퍼트는 DataColumnCollection 타입이면 열의 배열이다. DataColumnCollection클래스는 열을 추가, 제거, 검색하는 Add, Remove, IndexOf 등의 메소드를 제공한다. 순거값과 이름으로부터 칼럼을 찾을수 있는 읽기 전용의 인덱서를 정의한다.

ex. 첫번째 칼럼을 얻고 싶다면 DataColumn Col = MyTable.COlumns[0];

컬렉션은 항상 제로 베이스(Zero Base)이므로 첫번째 요소의 첨자가 0이다.

DataType - DataColumn 클래스의 DataType 프로퍼티는 열이 어떤 타입의 데이터를 저장하는지, 즉 데이터의 형태가 어떠한지를 지정한다.

SQL서버타입 닷넷 타입
INT int32
BIGINT int64
NVARCHAR, NCHAR String
DATETIME DateTime
BIT Boolean
DECIMAL Decimal
VARBINARY byte[]

Constraints  - 테이블의 Constraints 프로퍼티는 ConstraintCollection 타입이며 제약조건의 배열이다. 제약 조건은 테이블에 입력될수 있는 값의 종류를 제한한다는 면에서 테이블의 구조를 결정하는 스키마의 일부로 취급된다. 제약조건에 의해 테이블에 무의미한 값이 입력되는 것이 방지되어 무결성이 유지된다.

 

테이블의 실제 데이터를 저장하는 것은 행(Record)이며 행은 DataRow 클래스로 표현한다. 이 클래스는 특이하게도 공개된 생성자가 정의되어 있지 않아 코드에서 직접 객체를 생성할수 없다. 행은 테이블의 스키마구조에 맞게 생성되야 하므로 일반적인 생성구문을 정의할수 없다.

행의 구조를 정의하기 위해서는 열과 제약조건이 먼저 정의되어야 하며 행은 테이블 내에서만 존재할수 있으므로 단족으로 생성할수 없도록 되어 있다. 행을 만들때는 DataTable 클래스의 NewRow메소드를 호출해야 하며 이 메소드는 테이블의 스키마를 참조하여 열 컬렉션을 제대로 저장할수 있는 행 객체를 생성한다. 테이블의 스키마가 먼저 확정되어야 행을 생성할수 있는 것이 당연하다.

 

테이블생성

ex.

 private DataTable MakeSaleTable()
        {
            DataTable tbSale = new DataTable("tbSale");

            DataColumn col;
            DataRow row;

            col = new DataColumn("OrderNo", typeof(Int32));
            col.AllowDBNull = false; > Primary Key 가 되는 열 생성.
            col.Unique = true;

            col.AutoIncrement = true;
            col.ReadOnly = true;
            tbSale.Columns.Add(col);

            tbSale.PrimaryKey = new DataColumn[] { col };

            col = new DataColumn("Customer", typeof(string));
            col.MaxLength = 10;
            col.AllowDBNull = false;
            tbSale.Columns.Add(col);

            col = new DataColumn("Item", typeof(string));
            col.MaxLength = 20;
            col.AllowDBNull = false;
            tbSale.Columns.Add(col);

            col = new DataColumn("ODate", typeof(DateTime));
            col.AllowDBNull = false;
            tbSale.Columns.Add(col);

            row = tbSale.NewRow();
            row["Customer"] = "김태희";
            row["Item"] = "제모기";
            row["ODate"] = new DateTime(2008, 1, 1);
            tbSale.Rows.Add(row);

            row = tbSale.NewRow();
            row["Customer"] = "차예련";
            row["Item"] = "블라우스";
            row["ODate"] = new DateTime(2008, 1, 2);
            tbSale.Rows.Add(row);

            row = tbSale.NewRow();
            row["Customer"] = "이연희";
            row["Item"] = "스커트";
            row["ODate"] = new DateTime(2008, 1, 3);
            tbSale.Rows.Add(row);

            row = tbSale.NewRow();
            row["Customer"] = "문근영";
            row["Item"] = "교복";
            row["ODate"] = new DateTime(2008, 1, 4);
            tbSale.Rows.Add(row);

 

 tbSale.AcceptChanges(); > 없어도 화면에 출력을 되지만 이 함수를 호출하게 되면 RejectChanges를 호출해도 Data 삭제 되지 않는다.


            return tbSale;
        }

 

 

테이블 편집

그리드에서의 편집

데이터를 편집하는 가장 편리한 방법이 화면의 그리드 컨트롤을 사용하는 것이다.그리드를 엑셀 워크시트라고 생각하고 원하는 셀로 이동하여 편집하면 된다. 왠만한 가능은 모두 지원된다. 이것이 가능한 이유는 DataTable객체와 그리드 콘트롤이 바인딩되어 있기때문인데 그리드뿐만 아니라 테이블과 바인딩되는 모든 콘트롤에 동일한 기능이 제공된다. 그리드는 바인딩된 테이블을 표시할뿐 아니라 사용자로부터 입력을 받아서 테이블로 다시 보내기도 하며 테이블의 제약 조건을 참조하여 엉터리 데이터가 입력되는 것을 막아주기까지 한다. 그러나 그리드는 어디까지나 최종사용자가 직접 조작하는 콘트롤이지 코드에서 그리드를 통해 데이터를 편집할수는 없다.

검색

테이블에 있는 레코드 중 지정한 조건을 만족하는 것을 찾는 것이다. 삭제나 수정을 하려면 먼저 대상 레코드를 찾아야 하므로 편집을 하기전에 먼저 해야 한다.

첫번째는 행 컬렉션의 Find 메소드를 호출하는 것이다.

public DataRow Find(Object key)

public DataRow Find(Object[] keys)

두번째는 DataTAble의 Select 메소드를 호출하는 것이다.
public DataRow[] Select(string filterExpression, string sort)

수정

ex.

 string filter = "Name = '
  " + textBoxAge2.Text + " ' ";
            DataRow[] Result = tbPeople.Select(filter);
            if (Result.Length != 0)
            {
                Result[0]["Age"] = Convert.ToInt32(textBoxNewAge.Text);
            }
            else
            {
                MessageBox.Show("그런 사람은 없어요");
            }

입력받은 이름을 찾아서 그 사람의 나이를 바꾸는 예제이다. 여기서 중요한 것은 Name =  다음에 이름을 문자열 기호 " '' 로 묶어주는 것이다.

ex.

 string filter = "Name = '" + textBoxDelete.Text + " ' ";
            DataRow[] Result = tbPeople.Select(filter);
            if (Result.Length != 0)
            {
                Result[0].Delete();
            }

Delete가 휴지통에 버리는 거라면 Remove는 완전삭제를 의미한다. 가급적 Delete를 권장한다.

그리고 수정, 삭제 후 .RejectChanges();를 호출하면 원래 상태로 돌아간다. 그러니까 DataSet에 반영하려면 .AcceptChanges();을 꼭 호출해줘야 한다.

 

 

 

 

DB 기초에 관련된 사이트 winapi/SQL

쿼리문의 종류

-DDL(Data Definition Language) - DB 오브젝트를 생성, 삭제, 변경한다. CREATE, DROP, ALTER 등의 명령이 있다. 주로 DB를 디자인하는 관리자들이 이 부류의 명령을 사용한다

-DML(Data Manipulation Language) - DB를 조작한다.  SELECT, INSERT, DELETE, UPDATE 명령 등이 있다. 응용프로그램 개발자들은 주로 이 명령을 사용하여 DB의 데이터를 조사하거나 삽입, 삭제, 변경한다.

-DCL(Data Coltrol Language) - 사용자와 권한을 관리하는 명령이다. GRANT. DENY, REVOKE 등이 있다. DBA들이 주로 쇼앙하는 일반 응용 프로그램 개발자들은 이 명령들을 사용할 일이 거의 없다. 실제로 빈도가 아주 낮다.

 

정규화

제1정규화 - 필드에 저장되는 데이터를 원자화(atomivity)하는 것이다.

제2정규화 - 기본키가 아닌 모든 필드는 기본키에 종속되도록 하는 것이다. 여기서 종속된다는 표현은 긴본키를 알면 나머지 정보도 알수 있다는 뜻이다.

제3정규화 - 기본키가 아닌 일반필드끼리는 서로 종속되지 않는다. 일반 필드끼리는 독립적이어야 한다는 규칙이며 상호종속적인 필드는 분리해야 한다.

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 9. 5. 15:49

닷넷프로그래밍정복 김상형저 가메출판사

 

콘트롤 - 사용자와 직접 통신하는 유저 인터페이스 장치이다. 사용자에게 프로그램의 실행 결과나 현재상태를 보여주기도 하고 사용자로부터 명령이나 옵션을 입력받기도 한다.

가장 흔하고 대표적인 컨트롤이 바로 버튼이다.

 

양방향 편집

속성 창에서 편집하나 코드를 직접 수정하나 결과는 마찬가지이며 한쪽의 편집 결과가 다른 쪽에 즉시 영향을 미친다. 이런 방식을 양방향(Two Way) 편집이라고 하는데 상황에 따라 편리한 쪽을 선택하면 된다.

 

 

값 입력

텍스트 박스 - 키보드로 문자열을 입력받는 컨트롤이다.

정보나 실수를 입력받는 컨트롤은 따로 존재하지 않으며 텍스트 박스로 입력받는다. 수치값을 입력받더라도 키보드로부터 입력되는 모든 정보는 문자열 타입이르모 일단 문자열을 입력받은 후 원하는 타입으로 변환해서 사용해야 한다.

 

리스트 박스는 스스로 이벤트를 처리할수 있는 컨트롤이며 목록만 제공하면 항목들을 그리는 것도 직접처리한다. 그런데 컬렉션 편집기에서 추가할수 잇는 항목은 문자열뿐이므로 리스트박스가 관리하고 그릴수 잇는 항목도 문자열뿐이다. 그러나 리스트 박스가 아닌 오너, 즉 부모가 직접 그리기를 하면 문자열뿐 아니라 그래픽으로 된 내용물을 그릴수 있다.

이런 식으로 오너가 직접 그리기를 수행하는 콘트롤을 오너 드로우(Owner Draw)라고 한다. 리스트 박스뿐만 아니라 대부분의 콘트롤은 DrawMode 프로퍼티로 오너 드로우 방식을 지정한다. 이 프로퍼티가 None이면 오너 드로우 기능을 쓰지 않는 것이고 None이 아니면 부모가 직접 내용물을 그릴수  있도록 DrawItem 이벤트를 보낸다. 오너 드로우는 항목의 높이를 결정하는 방식에 따라 두가지가 있다. DrawMode 프로터티 값은 NMormal과 다음이 있다.

  • OwnerDrawFixed - 항목들의 높이가 모두 일정하다. 항목의 높이는 ItemHeight  프로퍼티로 딱 한번만 지정하면 된다
  • OwnerDrawVariable - 항목의 높이가 개별적으로 모두 다르다. 따라서 프로퍼티로 항목의 높이를 지정할수는 없으며 각 항목에 대해서 MEasureItem이벤트로 질문을 하며 이 이벤트에서 항목의 높이를 결정한다.

항목을 그려야 할떄는 부모에게 DrawItem 이벤트가 전달된다. 항목을 그리는 주체가 부모이므로 리스트 박스는 그리기를 할 때마다 부모에게 이벤트를 보내 어떤 항목을 그려달라는 것을 부탁한다. 이 이벤트의 인수로  DrawITemEventArgs객체가 전다뢴다. 이 객체에는 항목을 그리는데 필요한 정보들이 프로퍼티로 제공되며  ㄸ또 그리기에 필요한 여러가지 메소드로 포함되어 있다. 프로퍼티는 그리기에 필요한 정보들을 전달하므로 모두 읽기 전용이다.

 

ex.

        private DashStyle Dash = DashStyle.Solid;

        public JohnForm()
        {
            InitializeComponent();
        }
        private void JohnForm_Paint(object sender, PaintEventArgs e)
        {
            Pen P = new Pen(Color.Black, 2);
            P.DashStyle = Dash;
            e.Graphics.DrawRectangle(P, 300, 20, 140, 140);
        }
        private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
        {
            Pen P = new Pen(Color.Black, 2);
            P.DashStyle = (DashStyle)e.Index;
            e.DrawBackground();
            P.Color = e.ForeColor;
            e.Graphics.DrawLine(P, e.Bounds.Left + 10, e.Bounds.Top + 10,
                e.Bounds.Right - 10, e.Bounds.Top + 10);
            e.DrawFocusRectangle();
        }
        private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            ListBox L = (ListBox)sender;
            Dash = (DashStyle)L.SelectedIndex;
            Invalidate();
        }

 

콤보박스 - 리스트박와 텍스 박스를 결합한 콘트롤이다. 평소에는 텍스트박스처럼 보이지만 드랍다운을 펼치면 리스트 박스가 열려 목록 중 하나를 고를 수 있다. 리스트 박스와는 달리

목록에 없는 것도 텍스트 박스에 직접 입력해넣을수 있고 필요할때만 드랍다운을 펼치므로 자리를 적게 차지 한다는 이점이 있다. 그러나 드롭 다운을 펼치기 전에는 목록을 볼수 없고 선택을 변경하기 위해 두번 클릭해야 한다는 점이 불편하다.

 

컨트롤의 컬렉션

폼이나 그룹박스, 패널처럼 다른 컨트롤을 자식으로 거느릴수 있는 컨트롤을 컨테이너라고 한다. 컨테이너는 ControlCollection 타입의 Controls 프로퍼티로 자신에게 소속된 컨트롤의 목록을 유지하고 관리한다. CotrolCollection 클래스는 쉽게 말해 콘트롤의 배열인데 목록을 관리하는 메소드와 프로퍼티들이 정의되어 있다.

ex.

 private Button btn;

 

      private void Btn_Click(object sendear, EventArgs e)
        {
            MessageBox.Show("실행 중에 만든 버튼을 클릭했습니다");
        }
        private void JohnForm_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && btn == null)
            {
                btn = new Button();
                btn.Location = new Point(400, 300);
                btn.Size = new Size(150, 25);
                btn.Name = "RunButton";
                btn.Text = "실쟁중에 만든 버튼";
                btn.Click += new EventHandler(Btn_Click);

                Controls.Add(btn);
            }
            else if (e.Button == MouseButtons.Right && btn != null)
            {
               
                //Controls.foreach(Control tmp in Controls.
                Controls.RemoveAt(0);

                btn = null;
            }

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 9. 2. 17:32

닷넷프로그래밍정복 김상형저 가메출판사

 

Image class

GDI+가 GDI에 비해 월등히 우수한 점은 압축이미지를 다룰수 있다는 점이다. GDI는 이미지는 처리할수 있지만, 압축하지 않은 거대한 BMP파일만 쓸수 있어 활용도가 제한적이다. 반면에 GDI+는 JPG, GIF, PNG, TIFF 등의 압축효율이 높고 대중적인 파일들을 자유롭게 사용할수 있다 Image 클래스는 성성자가 제공되지 않는 추상클래스이므로 Image 타입의 객체를 직접생성할수 없다. 다만 대표타입으로 파생클래스인  Bitmap이나 Metafile 클래스의 객체를 가르키기만 한다.

ex.

 Image i = Image.FromFile("tiffany01.jpg");
            e.Graphics.DrawImage(i, 0, 0);

예제 구현의 편의상 Paint이벤트에 파일을 직접 출력했지만 이미지 같은 거대한 객체는 Load이벤트에서 미리 읽어두어야 한다.

 

DrawImage

 

비트맵 - Image로부터 파생되는 Bitamp클래스는 비트맵 그 자체를 표현하며 비트맵을 구성하느느 래스터 데이터를 관리한다. Image는 존재하는 이미지만 출력하고 과리하는 일반적인 클래스인데 비해 Bitmap은 이미지를 생성하거나 직접 조작할수 있다는 점에거 기능이 더 많다.

이러한 비트맵은 다양한 용도로 쓰이지만 주로 메모리상의 임시적인 출력표면으로 사용된다. 화면으로 출력을 내보내듯이 비트맵의 표면으로 출력을 내 보낼수 있다. 이런 목적으로 사용되는 비트맵을 오프스크린 비트맵 또는 가상화면이라고 한다. 가상 화면으로 출력을 하려면 먼저 Graphics 클랫의 다음 정적 메소드로 비트맵을 출력표면으로 하는 Graphics객체 얻어야 한다.

ex. MouseClick 핸들러에서

            if (e.Button == MouseButtons.Left)
            {
                Graphics G = Graphics.FromImage(bm);
                G.Clear(BackColor);
                Random R = new Random();
                for (int i = 0; i < 500; i++)
                {
                    SolidBrush Br = new SolidBrush(Color.FromArgb(R.Next(256),
                        R.Next(256), R.Next(256), R.Next(256)));
                    G.FillEllipse(Br, R.Next(600), R.Next(400), R.Next(70) + 30, R.Next(70) + 30);

                }
                Invalidate();
            }

이 이벤트를 Paint에서 처리한다면?

 e.Graphics.Clear(BackColor);
            Random R = new Random();
            for (int i = 0; i < 500; i++)
            {
                SolidBrush Br = new SolidBrush(Color.FromArgb(R.Next(256),
                    R.Next(256), R.Next(256), R.Next(256)));
                e.Graphics.FillEllipse(Br, R.Next(600), R.Next(400), R.Next(70) + 30, R.Next(70) + 30);

            }

이렇게 처리했을때 문제점은

  1. 그림을 그리는 중간 과정이 사용자 눈에 적나라하게 보인다는 점인데 시간이 오래 걸리는 것은 할수 없지만 중간과정을 보이는 것은 깔끔한 동작이 아니다. 백그라운드에서 이 작업을 미리 내놓는다면 더 빠르게 출력할수도 있다
  2. 이미 완성한 그림을 Paint 이벤트를 받을때마다 다시 그려야 한다는 점이다. 아무리 복잡한 그림이라도 메모리상의 비트맵에 미리 준비해두었다면 다시 그리기를 할떄는 완성된 그림만 전송하여 훨씬 더 빠르게 그릴수 있을 것이다.

 

 

더블버퍼링.

ex

  public partial class JohnForm : Form
    {
        int ex = 10, ey = 100;
        const int r = 15;


        public JohnForm()
        {
            InitializeComponent();
            timer1.Start();
        }

        private void JohnForm_Paint(object sender, PaintEventArgs e)
        {
            int x, y;

            for (x = 0; x < ClientRectangle.Right; x += 10)
            {
                e.Graphics.DrawLine(Pens.Black, x, 0, x, ClientRectangle.Bottom);
            }

            for (y = 0; y < ClientRectangle.Bottom; y += 10)
            {
                e.Graphics.DrawLine(Pens.Black, 0,y, ClientRectangle.Right,y);
            }

            e.Graphics.FillEllipse(new SolidBrush(Color.LightGreen), ex - r, ey - r, r * 2, r * 2);
            e.Graphics.DrawEllipse(new Pen(Color.Blue, 5), ex - r, ey - r, r * 2, r * 2);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            ex += 6;
            if (ex > ClientRectangle.Right) ex = 0;
            Invalidate();
        }
    }

화면은 주기적으로 지워졌다 그려졌다를 반복하며 사용자 눈에 지워진 화면과 그려진 화면이 교대로 보이는 것이다. 이 문제를 해결하는 가장 간단한 방법은 폼의 DoubleBuffered 프로퍼티를 true로 바꾸는 것이다. 닷넷2.0에서 추가되었다. 하지만 프레임워크에 의해 자동으로 더블 버퍼링을 하다보니 속도가 느리며 언커버될때나 복구될때 다시 그릴필요가 없을떄도 완전히 다시 그린후 더블버퍼링을 하므로 효율적이지 못하다.

ex.

      int ex = 10, ey = 100;
        const int r = 15;
        Bitmap bm;


        public JohnForm()
        {
            InitializeComponent();
            timer1.Start();
        }

        private void JohnForm_Paint(object sender, PaintEventArgs e)
        {
            if (bm != null)
            {
                e.Graphics.DrawImage(bm, 0, 0);
            }         
        }
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            //base.OnPaintBackground(e);
        }
        private void timer1_Tick(object sender, EventArgs e)
        {          
            int x, y;
            if (bm == null || bm.Width != ClientSize.Width || bm.Height != ClientSize.Height)
            {
                bm = new Bitmap(ClientSize.Width, ClientSize.Height);
            }
            Graphics g = Graphics.FromImage(bm);
            g.Clear(SystemColors.Window);

            ex += 6;

            if (ex > ClientRectangle.Right) ex = 0;

            for (x = 0; x < ClientRectangle.Right; x += 10)
            {
                g.DrawLine(Pens.Black, x, 0, x, ClientRectangle.Bottom);
            }
            for (y = 0; y < ClientRectangle.Bottom; y += 10)
            {
                g.DrawLine(Pens.Black, 0,y,  ClientRectangle.Right, y);
            }

            g.FillEllipse(new SolidBrush(Color.LightGreen), ex - r, ey - r, r * 2, r * 2);
            g.DrawEllipse(new Pen(Color.Blue, 5), ex - r, ey - r, r * 2, r * 2);

            Invalidate();
        }

타이머이벤트는 비트맵 객체를 생성하고 이 비트맵에 화면을 그린다.  Paint에서는 타이머이벤트로 생성한 화면을 고속전송하여 보여주기만 할뿐이다.

 

메타파일.

이미지를 저장하는 방식은 크게 래스터벡터 두가지로 구분된다. 래스터 이미지는 점의 집합으로 이미지의 모양을 기억하는 방식으로 BMP, JPG 등 흔히 사용하는 그래픽 파일이 모두 이 방식을 사용한다. 이미지를 저장하는 방식이 비디로 램의 물리적인 구조와 유사하므로 출력속도가 빠르고 얼마든지 복잡한 이미지도   표현할수 있다.

이에 비해 벡터능 이미지를 그리는 명령들을 부호화하여 저장하는 방식이며 명령들을 순서대로 재쟁하여 원래의 이미지를 복원한다. 명령을 재생할때 일정한 비율을 곱하여 확대할수 있으며 확대를 해도 이미지가 전혀 손상되지 않는다. 뙤 각 도형을 그리는 명령들이 부호화되어 저장되어 있으므로 개별 도형들을 분리하여 편집할수 있다. 하지만 벡터는 사진 같은 정교한 이미지를 표현하기에는 적합하지 않으며 명령을 일일이 재생해야 하므로 속도가 느린 것이 단점이다.

GDI+에서 벡터이미지를 표현하는 클래스는 Metafile이며 Image로부터 파생된다.

 

 

이미지고급

포맷변환 - 메모리상에서 생성한 이미지를 파일로 만들수도 있고 파일로부터 읽은 이미지를 원본과 다른 포맷으로 변환할수 있다. 어차피 그래픽 파일의 포맷은 압축방식이 조금씩 다를뿐이지 저장하는 대상은 동일하므로 상호변환할수 있다.

 

이미지변경 - GDI+는 간단하게나마 이미지를 편집하거나 변형하는 메소드를 제공한다.

  • 회전 - RotateFile
  • 스레쉬홀드 - SetThreshold 어떤 임계치르 ㄹ기준으로 색상 요소를 0이나 255로 내림 또는 올림하는 것이다. 임계치는 0.0~1.0까지의 값을 가지는데 0은 색상요소가 전혀 없는 것이고 1.0이면 최대 강도인 255를 의미한다. GDI+는 색상의 강도를 표현할때 0~255까지의 정수를 사용하지 않고 대신 0.0~1.0 사이의 실수를 사용한다. 각 색상요소가 임계치보다 작으면 0이되녹 임계치보다 크면 255가 되어 이미지의 색상이 훨씬 단순해진다.
  • 감마 - SetGamma 이미지의 밝기에 영향을 미치는 값이며 감마값에 따라 이미지의 밝기나 대비에 변화가 발생한다.
  • 밝기 조정 - SetColorMatrix 이미지의 색상들을 개별적으로 변경하고 싶을떄는 색상 매트릭스를 사용한다.
  • 반전 - 반전이란 모든 색상을 반대로 뒤집는 것이다.
  • 그레이스케일 - 그레이스케일은 모든 색의 삭생정보를 제거하고 밝기 정보만을 남겨 사진을 탈색시키는 것이다.
  • 색상 변경 - SetRemapTable 이미지의 특정 색상을 다른 색상으로 변경한다. 한꺼번에 여러개의 색상을 변경할수도 있다.

 

 

리소스

문자열

리소스란 코드의 반대되는 의미로 프로그램 실행ㄹ에 필요한 데이터를 말하며 문자열, 아이콘, 이미지 등이 리소스의 예이다. 프로그램의 동작을 정의한다기보다는 외형을 꾸미는 장식에 사용되는 것들을 리소스라고 한다.

문자열을 하드코딩하면 수정하기도 힘들고 다국어 적용하기도 힘들다. 그래서 리소스를 이용해서 구현하는 것이 좋다.

ex.

private void Form1_Paint(object sender, PaintEventArgs e)
        {
            string msg = Properties.Resources.String_Hello;
            e.Graphics.DrawString(msg, Font, Brushes.Black, 10, 10);
            msg = Properties.Resources.String_Anbu;
            e.Graphics.DrawString(msg, Font, Brushes.Black, 10, 30);
        }

 

이미지

앞의 예제에서는 FromFile 메소드를 이용해서 하드디스크의 이미지 파일을 읽었다. 호출형태는 간단하지만, 항상 실행파일과 같은 경로에 이미지가 반드시 있어야 하고 만약, 실수로 이미지 파일을 누락했다가거나 이미지 파일이 손상되었다면 파일을 읽지 못해 예외가 발생할 것이다. 프로그램이 제대로 동작할 것이라는 것을 보장할수가 없다. 또한 실행 중에 하드 디스크의 이미지를 읽어 들이려면 아무래도 속도가 늦다. 그래서 이미지를 실행파일의 리소스에 넣어서 관리한다.

리소스로 작성된 이미지는 컴파일할때 실행파일과 합쳐진다. 코드에서 리소스를 사용할떄는 리소스에 붙인 이름으로 읽어들이면 된다.

실행파일에 이미지가 완전히 통합되어 있으므로 별도의 부속파일없이 실행파일만으로도 잘 동작한다.

 

리소스는 링크 또는 포함 두가지 방식으로 프로젝트에 추가된다. 링크는 이미지의 실제 데이터는 외부파일에 두고 리로스에 그 경로만 기록하는 방식이다. 포함은 이미지의 실제 데이터가 텍스트 형태로 인코딩되어 리소스 파일에 기록되는 방식이다. 오해하지 말 것은 두 방식 모두 리소스를 관리하는 방법상의 차이일뿐 컴파일된 결과는 동일하다는 것이다.

 

사운드

리소스 추가하여 호출하는 방식은 다른 포맷과 동일하다.

ex,

SoundPlayer P = new SoundPlayer();

P.Stream = Properties.Resource.Dingdong;

P.Play();

 

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 9. 2. 14:33

닷넷프로그래밍정복 김상형저 가메출판사

이번 장은 완전히 실습장입니다.

 

닷넷의 기본적인 그리기 엔진은 Win32  GDI 모듈의 업그레이드 버전인 GDI+이다.  GDI+는 네이티브 C++라이브러리로 만들어졌고 닷넷은 GDI+를 외부호출로 구현한다.

 

GDI와 GDI+의 주요한 차이점 및 GDI+ 특징

  1.  GDI는 DC에 그리기 정보를 저장하고 그리기 함수들은 DC의 정보를 이용한다. DC에는 현재 그리기 상태에 대한 모든 정보가 저장되어 있고 함수들은 DC의 핸들을 받아 이 DC로부터 필요한 정보를 얻는다. 그래서 그리기 상태를 변경하려면 DC의 상태를 먼저 변경해야 한다. 예를 들어 파란색으로 선을 긋고 싶다면 파란색 펜을 만들어 DC에 선택한 후 LineTo함수를 호출해야 한다.반면에 GDI+는 별도의 상태를 유지하지 않으며 그리기에 필요한 모든 정보를 메서드를 일일이 전달한다. 파란색 선을 긋고 싶으면 파란색 펜 오브젝트를 만든 후  DrawLine메소드로 이 펜을 전달한다.
  2. 그리기에 사용되는 펜, 브러시, 폰트 같은 오브젝트들은 모두 클래스로 정의되어 있다. 따라서 별도의 파괴코드를 작성할 필요가 없다. C++에서는 파괴자가 자동으로 파괴하며 C#에서는 GC가 정리한다. 수동으로 선택 해제 및 삭제를 해야 하는 GDI에 비해 훨씬 편리해졌을뿐 아니라 실수나 예외에 의해 리소스가 제대로 해제되지 않던 골치 아픈 문제가 사라졌다
  3. GDI 의 그리기 함수들은 항상 외곽선을 그리고 내부를 채운다. 만약 둘 중 하나를 그리지 않으면 투명 오브젝트를 사용해야 한다. GDI+는 외곽선을 그리는 Draw 메소드와 내부를 채우는 Fill메소드가 철저하게 분리되어 있다. 그래서 한쪽만 그릴때는 편리하지만 외곽선과 내부를 모두 그리려면 Draw메소드와 Fill메소드를 순서대로 호출해야 한다.
  4. 모든 문자열은 유니코드이므로 안시문자열을 출력하려면 유니코드로 변환한 후 출력해야 한다. 출력하는 문자열뿐 아니라 파일의 경로도 반드시 유니코드로 써야 한다.

 

모든 그리기 함수들은 Graphics 클래스의 메소드로 존재한다. 그래서 화면이든 프린터든 그리기 를 하려면 먼저 Graphics객체를 얻어야 한다. Graphics 클래스는 생성자가 private이어서 직접 생성할수 없고 봉인클래스라 파생도 할수 없다. Graphics 객체를 얻는 방법은

  1.  그리기 전용 이벤트인 Paint의 인수로 전달된 PaintEventArgs 객체로 부터 얻는다. 직접 생성한 것이 아니므로 받아서 사용만 할뿐 별도의 정리가 필요없다. 대부분의 그리기는 이방식으로 사용된다.
  2. Paint 이외의 이벤트나 일반 메소드에서 그리기를 할때는 Control 클래스의 CreateGraphics 정적메소드를 호출하여 Graphics 객체를 생성한다. 직접 생성한 것이므로 사용한 후 Dispose 메소드를 호출하여 가급적이면 빨리 해제하는 것이 좋다.

 

 

색상.

GDI+에서는 색상도 객체이다. Color라는 구조체로 선언되어 있다. FromArgb 정적 메소드를 이용해서 원하는 색을 배합할수 있다. 색은 RGB값읆 각각 넣어주면 된다

ex

Color Red = Color.FromArgb(255, 0, 0);

Color Green - Color.FromArgb(0,255,0);

Color Yellow = Color.FromArgb(255,255,0);

FromArgb 메소드로 원하는 색상을 직접 만들어 쓰는 방법 말고 141 기 선언된 색상을 선택할수 있다. 140개는 HTML 표준색상이고 1개는 투명색이다.

GDI+색상이 GDI 색상보다 우수한 점이라면 색상요소에 알파채널이 포함되어 있어 반투명 효과를 지원한다는 점이다.

ex

 private int Alpha = 128;

        public JohnForm()
        {
            InitializeComponent();
        }

        private void JohnForm_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.FillEllipse(Brushes.Firebrick, 10, 10, 150, 100);
            SolidBrush s = new SolidBrush(Color.FromArgb(Alpha, 255, 0, 0));
            e.Graphics.FillRectangle(s, 50, 50, 150, 100);
            e.Graphics.DrawString("위 : 투명도 증가, 아래 : 투명도 감소", Font, Brushes.GhostWhite, 10, 160);
        }

        private void JohnForm_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Up:
                    Alpha = Math.Max(0, Alpha - 25);
                    Invalidate();
                    break;
                case Keys.Down:
                    Alpha = Math.Min(255, Alpha + 25);
                    Invalidate();
                    break;

            }
        }

투명효과는 게임에서 효과적이고 배경의 그림과 복잡한 논리 연산을 해야 하므로 속도는 느린편이다. 그러니까 꼭 필요하지 않은 곳에 어설프게 사용해서는 안 된다.

 

시스템 색상 - 운용체제가 바탕 화면과 왼도우의 각부분을 그리기 위해 사용하는 색상이다. Color 객체로 표현하는 색은 한번 정해지면 언제까지나 그 색상만 표현하는 절대색인데 비해 시스템 색상은 사용자가 선택할수 있는 가변색이다. 바탕화면의 등록정보 대화상자에서 시스템 색생을 선택하며 테마에 의해 정의되기도 한다.

응용 프로그램에서 윈도의 각 부분을 직접 그려야 한다면 절대색을 사용하지 말고 반드시 시스템 색을 써야 한다. ex,Form 예제에서 배경색은 Control, ForeColor는 ControlText로 정해짐.

 

펜은 선을 긋는데 사용되는 그래픽 오브젝트이다. Graphics의 메소드 중 Draw로 시작하는 메소드들은 공통적으로 선을 그리는데 필요한 펜을 인수로 요구한다. 생성자는 다음과 같다.

public Pen(Color color, [.float width]) //Color 객체로 색상을 바로 지정.

public Pen(Brush brush, [.float width]) //Brush 객체로 무늬까지 같이 지정.

그리기와 관련된 대부분의 클래스들은 System.Drawing 네임스페이스에 포함되어 있어서 따로 참초추가할필요가 없다.

 

교점처리 및 정렬

ex

 Pen P = new Pen(Color.DodgerBlue, 15);
            P.Alignment = PenAlignment.Center;
            P.LineJoin = LineJoin.Miter;
            e.Graphics.DrawRectangle(P, 10, 10, 80, 80);

            P.Alignment = PenAlignment.Inset;
            P.LineJoin = LineJoin.MiterClipped;
            e.Graphics.DrawRectangle(P, 120, 10, 80, 80);

            P.Alignment = PenAlignment.Outset;
            P.LineJoin = LineJoin.Bevel;
            e.Graphics.DrawRectangle(P, 10, 120, 80, 80);

            P.Alignment = PenAlignment.Left;
            P.LineJoin = LineJoin.Round;
            e.Graphics.DrawRectangle(P, 120, 120, 80, 80);

            e.Graphics.DrawRectangle(Pens.Black, 10, 10, 80, 80);
            e.Graphics.DrawRectangle(Pens.Black, 120, 10, 80, 80);
            e.Graphics.DrawRectangle(Pens.Black, 10, 120, 80, 80);
            e.Graphics.DrawRectangle(Pens.Black, 120, 120, 80, 80);

 

 

브러쉬 - 면을 채우는 그래픽 오브젝트이다. Graphics 클래스의 Fill 로 시작되는 메소드들은 면적을 채우기 위한 브러쉬를 인수로 요구한다. 텏스트 출력문이나 펜 생성자도 브러쉬를 사용하는데 면적이 있는 모든 출력문에 브러쉬가 사용된다. 최상위의 Brush는 추상클래스이므로 객체를 생성할수 없으며 그리기와 관련된 특별한 메소드나 프로퍼티도 없다. 다만 대표타입으로서 브러쉬 전체를 칭하는 용도로만 사용된다.

 

그래디언트브러쉬 - 색상이 점점 변하는 브러쉬이다.

 

텍스처 브러쉬 - 이미지를 반복적으로 타일링하는 브러쉬이다. 이미지의 크기나 색상수에 특별한 제약이 없으므로 얼마든지 자유로운 모양의 브러쉬를 만들수 있다. 텍스트 브러쉬의 원점을 조정하려면 Matrix 객체를 생성하여 평행이동하는 좌표변환을 해야 한다.

 

 

그리기 메소드.

직선

 

면 - 타원과 사각형을 그리는 메소드

 

곡선 

 

텍스트 출력

DrawString - 씨로 치면 printf, win32에선 TextOut 해당하는 기본적인 출력문이다.

 

폰트 - 문자열의 모양을 정의하는 그래픽 오브젝트이며 Font 클래스로 표현한다. DrawSTring 메소드를 글꼴을 정의하는 Font  객체를 인수로 전달받는데 통상 폼의 Font 프로퍼티를 그대로 전달하여 디폴트로 사용한다.

폰트패밀리는 비슷한 특성을 가지는 폰트의 그룹이다. 폰트 패밀리에 크기와 스타일이 더해지면 실제출력에 사용할수 있는 폰트오브젝트가 된다.

사용자가 지정한 폰트가 없을 경우에 대체폰트를 찾게 된다. Win32는 글꼴이 발견되지 않을 경우를 대비해서 피치특성, 문자셋, 패밀리, 여러가지 정확도 등을 지정하며 지정한 폰트가 아니더라도 최대한 비슷한 폰트를 생성하도록 폰트맴핑 알고리즘이 정교하다. 반면에 닷넷은 고작 폰트의 이름밖에 지정하지 않으므로 어떤 폰트가 선택될지 예측하기 어렵다.

ex. 현재 시스템이 가지고 있는 폰트 리스트만들기

FontFamily[] Fms = FontFamily.Families;
            int x = 10, y = 10;

            foreach (FontFamily fm in Fms)
            {
                try
                {
                    Font F = new Font(fm, 15);
                    e.Graphics.DrawString(fm.Name, F, Brushes.Black, x, y);
                    y += 20;
                    if (y > ClientSize.Height - 20)
                    {
                        x += 300;
                        y = 10;
                    }

                }
                catch
                {
                }

            }

 

안티알리아싱

점단위로 그래픽을 출력하는 디지털 장비의 특성상 흰 바탕에 검정색으로 출력하면 계단 현상이 생긴다. 계단현상은 늘 있으나 얼마만큼 우리가 보기에 잘보이느냐의 문제이다. 그래서 색상차가 큰 경계부근에 중간 색상을 적당히 삽입하여 계단이 잘 안보이도록 하는 것이 안티알리아싱이다.

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 9. 1. 14:55

닷넷프로그래밍정복 김상형저 가메출판사

이번 장은 완전히 실습장입니다.

 

시스템에서 발생하는 일체의 변화에 대한 정보를 메시지라고 하며 닷넷에서는 이벤트라고 부른다.

이벤트에 핸들러 추가하기

ex MyForm.Paint += new PaintEventHandler(MyPaint);

 

static void MyPaint(object sender, PaintEventArgs e)
        {
            e.Graphics.DrawEllipse(Pens.Blue, 10, 10, 200, 200);
            e.Graphics.DrawRectangle(Pens.Red, 10, 10, 150, 350);


        }

delegate로 선언되어있으니까 += 연산자를 이용해서 추가해주면 되고 인자는 object와 .이벤트인자를 넣어주면 된다

 

가장 메소드 재정의하기 - OnPaint는 Control class 정의되어 있고 protected이다. 이것을 재정의하는 방법이 있다. 객체에 속한 메소드이니까 메세지를 처리해주는 주체인 sender는 인수로 받지 않는다. 객체 자신인 this가 바로 sender이기때문이다. 핸들러 등록 없이 함수를 재정의하면 된다.

ex

 protected override void OnPaint(PaintEventArgs e)
        {
            //base.OnPaint(e);
            e.Graphics.DrawEllipse(Pens.Blue, 10, 10, 200, 200);
        }

Paint 이벤트에 핸들러를 추가하는 것과 OnPaint 가상메소드를 재정의하는 것의 차이는 무엇일까.

WM_PAINT메세지 발생시 프레임 워크가 OnPaint 메소드를 호출하도록 되어 있고 OnPaint 이벤트에 등록된 핸들러를 차례로 호출하도록 되어 있다. 즉 omPaint가 우선순위가 더 높다.

폼에서 OnPaint를 재정의하지 않으면 Control의 OnPaint가 호출되어 등록된 핸들러를 호출하지만 OmPaint 재정의 하면 이 코드가 실행되지 않으므로 등록된 핸들러가 어떤 것도 호출되지 않는다. WM_PAINT 메시지에 대해 두 메소드를 다 실행하려면

ex.

 protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e); // 반드시 있어야 한다. 2008에는 기본적으로 재정의 하면 나온다.
            e.Graphics.DrawEllipse(Pens.Blue, 10, 10, 200, 200);
        }

OnPaint 가상메소드는 프레임 워크에 의해 호출된다는 점에서 미리 설치된 이벤트 핸들러라고 할수 있다

 

마법사의 핸들러 작성

  1.  디자이너에서 이벤트를 처리할 객체를 선택한다
  2. 속성창의 이벤트 페이지에서 처리할 이벤트를 선택한다
  3. 마법사가 이벤트 핸들러를 생성하고 연결한다
  4. 핸들러 본체에 코드를 작성한다.

    가상메소드의 이름은 항상 이벤트 이름 앞에 전치사 On을 붙여서 정의한다. 예를 들어 Some 이벤트가 있다면  이 이벤트를 호출하는 가상메소드는 OnSome이다.

    이벤트중엔 사용하지 않는 것들도 있다. Close Event는 닷넷2.0이후에 FormClosed로 바꾸어 쓰지만 과거 호환성을 위해서 남아있다. SizeChanged 이벤트는 Resize Event와 동일하니 선택해서 사용하면 된다. LocationChanged Event와 Move Event도 동일하다 VB지원때문에 중복해서 정의되어 있다.

 

 

키보드 입력

  1. ex.

    public JForm()
            {
                InitializeComponent();
                str = "";
            }

            private void JForm_Paint(object sender, PaintEventArgs e)
            {
                //e.Graphics.DrawEllipse(Pens.Blue, 10, 10, 200, 200);
                e.Graphics.DrawString(str, Font, Brushes.Gold, 10, 10);
                //Update();
            }

            private void JForm_KeyPress(object sender, KeyPressEventArgs e)
            {
                //str += e.KeyChar;
                if(e.KeyChar == ' ')
                {
                    str = "";
                }
                else
                {
                    str += e.KeyChar;
                }
                Invalidate();
            }
    invalidate()는 화면을 무효화하여 차후 Paint 이벤트를 발생시키는 역할을 한다. 만약 Paint 이벤트를 즉시 처리하려면 Update()를 호출하거나 Invalidate와 Update를 같이 수행하는  Refresh 메소드를 호출해야 한다. 모든 그리기는 Paint 이벤트에서 해야 함을 유의한다.

- Form의 Size.Witdh/Height와 Width/Height는 같은 값인거 같다

마우스 입력

마우스 메세지

click event와 mouse click 이벤트의 차이.

mouse Click은 마우스에서 클릭이 일어났을때 발생하는 이벤트이고 컴포넌트에서 Enter 키 같은 것으로 클릭이 일어나면 발생하지 않는다.

click 이벤트는 모든 컴포넌트의 모든 종류의 클릭-마우스를 사용하던 말던-과 더블클릭 이벤트 발생시에 발생하는 이벤트이다. 그러므로 더블클릭의 경우 Click 이벤트는 두번발생한다.

 

휠마우스

스크롤 delta 는  default 는 120 이 가능하다.

속성창에 MouseWheel가 누락되어 있는 버그는 VS2005/VS2008 모두 수정되어 있지 않아서 직접 핸들러를 달아줘여 한다.

 

마우스의 들어오고 나오고 관리

마우스가 콘트롤 안에 들어가게 되면 MouseHover 이벤트가, 마우스가 콘트롤 밖으로 나가면 MouseLeave Event가 발생한다.

 

폼의 이벤트

  • Load - 폼이 로드되어 메모리로 올라올때, 그리고 화면에 표시되기 직전에 발생한다. 폼의 일회적인 초기화에 적합하다
  • FormClosed - 폼이 닫힐때, 즉 생애를 마치고 파괴될때 발생한다. Load에서 할당한 리소스를 이 이벤트에서 해제한다. 닷넷2.0이전엔 Closed 이벤트를 썼으나 FormClosed 바뀌었다
  • FormClosing - 폼이 닫히기 직전에 발생한다.

 

생성자와 Load 이벤트

생성자 대신에 Load이벤트 핸들로 초기화 할수 있다. 호출순서는 생성자 호출 다음에 Load 이벤트가 발생한다.

 

ex.

public JForm2()
        {
            //ar = new ArrayList();
            Status = "알수 없음";
            InitializeComponent();
            if (true)
            {
                MessageBox.Show("생성자 초기화에 실패하여 실행을 계속 할수 없습니다",
                    "이거나 이걸 어째");
                Close();

            }
        }

이 예제는 예외가 발생한다. 이유는 생성자가 하라는 초기화는 안 하고 close로 폼을 닫아버렸기때문이다.

 

크기변경

  • Resize - 콘트롤의 크기가 변경될 때, 즉 Size 프로퍼티가 바뀔때마다 발생한다. 별도의 인수는 전달되지 않으므로 변경되 크기는 sender 인수를 Control로 캐스팅한 후 Size 프로퍼티를 통해 조사해야 한다. 컨트롤이 최소 생성될때는 이 이벤트가 발생하지 않는다.
  • Layout - 폼의 크기가 바뀌거나 차일드의 위치를 변경할때 또는 차일드를 추가ㅡ 제거할떄마다 전달된다
  • SizeChanged - Resize와 동일한 이벤트
  • ClientSizeChanged - ClientSize 프로퍼티가 변경될때마다 발생한다. Resize 이벤트와 발생 시점이 거의 동일핟.

 

상태변화

폼의 상태나 프로퍼티가 조금이라도 바뀔때마다 각종 이벤트들이 발생한다. 프로퍼티의 변경에 대한 이벤트들은 공통적으로 ~Changed로 되어 있다.

  • Activard, Deactivated - 활성화 상태가 변경될때
  • Shown - 폼이 최초로 보일때 한번만 발생한다
  • GotFocus, LostFocus - 포커스 변경시 발생
  • VisibleChanged - Visible 속성이 변경될때 발생
  • ForeColorChanged, BAckColorChanged - 전경색, 배경색이 변경될때 발생
  • FontChanged - Font 프로퍼티가 변경될때 발생

이 중에 Shown 이벤트가 유용하다. 예를 들어 프로그램이(폼이) 뜬 상태에서 등록을 유도하는 메세지창을 띄우는 경우 유용하다. Load 이벤트를 쓴다면 폼이 로드되어 보이기 전에 실행되겠지만 Shown은 폼은 로드가 된 다음에 바로 실행된다는 장점이 있다, Win32에는 이에 대응하는 적절한 메세지가 없다.

 

 

이벤트 보기

이벤트는 정확한 발행 시점곽 순서를 아는 것이 중요하다. 닷넷에는 win32의 스파이 같은 프로그램이 없으므로 필요한 이벤트가 있다면 직접 등록해서 테스트해봐야 한다

 

타이머

시계 - 닷넷에서는 타이버는 Timer class로 표현한다. 하지만 얼추 일정한 간격으로 이벤트를 발생시킬뿐 실행환경에 따라서 발생주기가 어긋나기도 한다. 믿을수 없다.

ex.

 public partial class TimerForm : Form
    {
        private string Time;


        public TimerForm()
        {
            InitializeComponent();
        }

        private void TimerForm_Load(object sender, EventArgs e)
        {
            Timer T = new Timer();
            T.Interval = 1000;
            T.Tick += new EventHandler(Form1_Timer);
            T.Start();

        }

        private void TimerForm_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.DrawString(Time, this.Font, Brushes.Black, 10, 10);
        }

        private void Form1_Timer(object sender, System.EventArgs e)
        {
            DateTime dt = DateTime.Now;
            Time = dt.Hour + " 시 " + dt.Minute + " 분 " + dt.Second + " 초 ";
            Invalidate();

        }

여기서 매번 Timer 이벤트가 발생할때마다 시각을 다시 조사하는 것은 Timer Event가 썩 믿음직 스럽지 않기때문에 확실한 값을 만들기 위해서 이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 8. 29. 16:59

닷넷프로그래밍정복 김상형저 가메출판사

WindowsForm 프로그램을 개발하려면

  1. 일반 프로젝트에서 출력형식을 Windows Application으로 고치고

System.Drawing / System.Windows.Forms 참조를 추가해주고 현재 클래스를 Form 상속을 받은 다음에 Application 에서 생성한 form 객체를 Run시키면 된다.

2.프로젝트 형식을 Windows Form Application을 선택해서 생성하면 된다. 여기서 Form class Windows를 호출하는 Form1.cs는 VS2003에서는 하나의 파일로 2005 이상에서는 두개의 파일로 나누어져 생성된다.Form1.csForm1.Desinger.cs로 분리되는데 이것은 같은 클래스를 분할선언해서 코드를 분리한 것일뿐 실제 하나의 클래스에 대한 내용이다.

분할해서 선언한 이유는 코드 추가하기 편하게 하고 원활한 기계화를 위해 명료한 형식성을 갖추기 위해서이다.

 

그래픽환경에서 가장 기본이 되는 부품이 윈도우이다. 닷넷에서는 Form이라고 부른다 System.Windows.Form에 정의되어 있다.

 

유틸리티 클래스

Application - 응용프로그램 자체를 나타내는 클래스이다. 공개된 생성자가 없기때문에 객체를 생성할수 없으며 봉인되 있어 상속도 할수 없다. 응용프로그램을 관리하는 정적 메소드의 집합이라고 생각하면 된다.

 

MessageBox - 사용자에게 짧은 문자열 메시지를 출력하는 조그만 대화상자로서 안내문이나 에러, 간단한 질문 등을 할 수 있다. 형식은 "지시문" "캡션" 버튼타입 으로 이루어진다

버튼 타입은 MessageBoxButtons 열거형으로 정의되어 있다.

 

Rectangle

사각형을 표현하는 방식은 시작점과 width, height 가 지고 표현하는 방법이 있고 Left,Top,Right,Bottom 으로 표현하는 방식이 있다. 전자는 XYWH라 하고 후자를 LTRB라 한다. Rectangle은 디폴트는 XYWH이다. Left, Right, Top , Bottom은 읽기전용프로퍼티이다.

 

SystemInformation - 현재 시스템 환경에 대한 여러가지 정보를 제공한다. 멤버들은 모두 정적이고 읽기전용이다.

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 8. 28. 15:45

닷넷프로그래밍정복 김상형저 가메출판사

쓰레드는 코드의 실행흐름이다.

쓰레드 생성 대리자

public delegate void ThreadStart()

public delegate void ParamiterizedThreadStart(Object obj)

둘 다 리턴 값은 없는 쓰레드는 생성후 독립적으로 실행되다가 작업이 완료되면 자동으로 파괴되는 것이므로 작업결과를 반환하지 않는다. 쓰레드로 전달한 작업내용이 없으면 인수가 없는 델리게이트 타입을 사용하고 전달한 인수가 있으면 인수가 있는 델리게이트 타입을 사용한다.

생성자는 쓰레드 객체를 만들기만 하고 실행을 즉시 하지 않는다. 쓰레드는 정지된 상태로 생성만 되며 이후 Start 메소드를 호출하여 시작한다. Suspend는 쓰레드 실행을 일시중지하고 Resume은 일시 중지된 쓰레드의 실행을 재개한다. Sleep 정적 메소드는 쓰레드의 실행을 일정시간 잠시 중지하는데 단위는 1/1000 초이다. 이 기간 동안 쓸데는 실행을 중지하고 다른 쓰레드를 위해 CPU를 양보한다. Sleep은 백그라운드 쓰레드의 실행속도를 제어하는 주요한 수단이다.

ex.

static void ThreadProc()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(i);
                Thread.Sleep(500);

            }
            Console.WriteLine("작업 쓰레드 종료");
        }


        static void Main(string[] args)
        {
            Thread t = new Thread(new ThreadStart(ThreadProc));

            t.Start();

            while (true)
            {
                ConsoleKeyInfo cki;
                cki = Console.ReadKey();
                if (cki.Key == ConsoleKey.A)
                {
                    Console.WriteLine("inserted A");
                }
                else
                {
                    Console.WriteLine("inserted {0}", cki.Key);

                }
                if (cki.Key == ConsoleKey.B)
                {
                    break;
                }

            }
            Console.WriteLine("주 쓰레드 종료");

}

}

카운트 증가와 키입력대기라는 두개의 작업을 병렬적으로 하는 예제이다. 작업쓰레드의 진입함수는 필요할 경우 쓰레드로부터 작업에 필요한 인수를 object타입으로 받을수 있다. 오브젝트 값은 진입함수의 인자로 object를 받는 것으로 수정하고 ThreadStart를ParameterizedThreadStart로 생성한 다음 Start시에 넣어주면 된다.

작업쓰레드는 생성후 자신의 소임을 다 하면 자동으로 종료된다. 통상 백그라운드 작업이란 영원히 실행되는 것이 아니라 주 쓰레드를 대신하여 시간이 걸리는 작업을 대신 하는 것이므로 진입함수가 종료되면 쓰레드도 끝나는 것이 자연스럽다. 만약 외부에서 쓰레드를 강제로 종료하고 싶다면 다음 메서드를 사용한다.

ex Thread.Abort() Thread.Join

Abort는 실행중인 쓰레드를 강제종료하고 필요한 정리작업을 한다. Join은 쓰레드가 완전히 종료되고 자원을 해제할때까지 대기하는 역할을 한다.

 

쓰레드의 프로퍼티

  • Name - 쓰래드의 이름을 지정하는 문자열. 지정하지 않으면 null이고 이름은 최초 딱 한번만 지정할수 있으며 실행 중에 변경하지는 못한다.
  • IsAlive - 쓰레드가 아직 살아있는지 확인
  • IsBackground - 배경쓰레드인지, 전경 쓰레드인지 조사 또는 지정한다. 주 쓰레드가 종료되었을때 모든 전경쓰레드가 종료되어야 응용프로그램이 무사히 종료할수 있다. 만약 전경 쓰레드 중 하나라도 실행중이라면 응용프로그램은 종료되지 않고 대기한다. 이에 비해 배경 쓰레드는 실행중이라도 응용프로그램 종료에는 별 영향을 주지 않는다. 디폴트는 false이고 전경쓰레드로 생성된다. 그래서 중요한 것은 전경쓰레드로, 배경이나 효과같이 덜 중요한 것은 배경쓰레드로 생성하면 좋다.
  • Prioirty - 쓰레드의 우선순위. 우선순위는 쓰레드가 CPU시간을 얼마나 많이 받아야 하는지를 지정하며 우선순위가 높을수록 더 많은 실행시간을 확보할수 있다. 우선순위는  ThreadPriority 열거형으로 표현하며 기본값은 Normal이다. 시스템이 쓰레드의 실행순서를 정밀하고 공평하게 제어하므로 가급적이면 우선순위는 조정하지 않는 것이 좋다. 하지만 백그라운드의 작업쓰레드보다 사용자의 입력을 받는 쓰레드는 더 중요한 일을 하므로 우선순위를 높여 반응성을 높이는 것이 바람직하다.
  • ThreadState - 쓰레드의 현재 상태를 조사한다. 읽기전용이다
  • CurrentThread - 현재 실행중인 쓰레드를 나타내는 정적 프로퍼티이다.

 

동기화

쓰레드는 프로세스에 속한 전역, 정적 자원이나 하드웨어 같은 본질적으로 전역인 자원을 공유하기때문에 하나의 자원을 두고 두개 의 쓰레드가 서로 싸울수 있다.이를 경쟁상태(Race Condition)하고 하고 경쟁을 해결하려면 한 쓰레드가 공유지원을 사용하는 동안 다른 쓰레드는 대기하도록 하는 동기화(synchronization)가 필요하다. 동기화를 하다보면 양쪽이 서로를 기다리는 교착상태(DeadLock)에 빠지기도 한다.

멀티쓰레드에서 동기화의 문제가 발생하는 근본적인 원인은 쓰레드의 실행순서가 비동기적이라는 것이다.

ex. 경쟁관계에 있는 쓰레드 예제

class Site
{
    public string name;
    public Site(string aname) { name = aname; }
}
 class ThreadExample1
    {
        private static Site site = new Site("www.winapi.co.kr");

        static void ThreadProc()
        {
            for (int i = 0; i <= 100; i++)
            {
                Console.SetCursorPosition(0, 0);
                Console.WriteLine("{0} 에서 {1}% 다운로드 중", site.name, i);
                Thread.Sleep(1000);
            }
        }

        static void DoSomething()
        {
            string old = site.name;
            site.name = "www.loseapi.co.kr";
            for (int i = 0; i <= 100; i += 10)
            {
                Console.SetCursorPosition(0, 1);
                Console.WriteLine("{0} 에서 {1}% 다운로드 중", site.name, i);
                Thread.Sleep(500);
            }
            site.name = old;
        }
        static void Main(string[] args)
        {
            Thread t = new Thread(new ThreadStart(ThreadProc));
            t.Start();
            Thread.Sleep(2000);
            DoSomething();
        }

이 예제는 사이트별로 따로 자원을 쓴다면 문제가 없는 코드이다. 하지만 실제로는 자원을 공유해야되서 동기화가 필요해진다. 동기화란 거창하게 생각할 필요 없이 한쪽에서 쓰는 동안 다른 쪽을 대기시키는 것이라고 생각 하면 된다.

ex.

static void ThreadProc()
        {
            for (int i = 0; i <= 100; i++)
            {
                lock (site)
                {

                    Console.SetCursorPosition(0, 0);
                    Console.WriteLine("{0} 에서 {1}% 다운로드 중", site.name, i);
                }
                Thread.Sleep(500);
            }
        }
        static void Main(string[] args)
        {
            Thread t = new Thread(new ThreadStart(ThreadProc));
            t.Start();
            Thread.Sleep(2000);
            lock (site)
            {

                DoSomething();
            }
        }

lock 블록에 잠그조가 하는 객체를 지정하면 이 객체를 액세스하는 쓰레드들은 한번에 하나씩 순서대로 실행된다. 주 쓰레드에서 DoSOmething을 호출하는 동안 lock블록으로 site객체를 잠근다. 이렇게 되면 다운로드 쓰레드는 lock 블록안으로 들어가지 못하고 잠시 대기한다. DoSomething은 이 때 site객체의 정보를 원하는 값으로 잠시 변경한 후 작업을 하고 완료후 원래대로 복구한다. 작업 완료후 site에 대한 잠금을 풀렴 대기하고 있던 쓰레드로 정상 동작을 계속할수 있다.

lock 블록에는 값타입의 변수는 지정할수 없으며 참조차입의 객체만 지정할수 있다. 이 때 lock에 잠그는 객체는 동기화가 필요한 블록에 대한 이롲ㅇ의 식별자 역할을 하므로 임의의 객체이기만 하면된다. site가 아니고 임의의 a객체를 선언하고 잠글수도 있지만 가급적 보호대상 자체를 잠그는 것이 의미가 명확하다.

 

C#에서 동기화를 하는 좀 더 일반적인 객체는 Monitor이다. Monitor는 Win32의 CriticalSection에 해당하며 동시에 두개의 블록이 같이 실행되지 못하도록 방지한다. 이 객체의Enter, Exit 메소드로 잠그고자 하는 객체와 범위를 지정하면 된다. 이 방법을 좀 더 쉽게 언어차원에서 제공하는 것이 lock이며 lock도 내부적으로 Monitor객체를 쓴다.

 

프로세스는 실행중인 응용프로그램을 의미한다. 하드 디스크에 실행파일 형태로 존재하는 프로그램이라는 개념과는 다른데 프로그램이 실행되어 메모리로 올라오면 비로소 프로세스가 된다. 프로세스는 실행에 필요한 메모리와 각종 핸들, 쓰레드를 담는 컨테이너이며 운영체제는 프로세스 단위로 자원을 할당하고 관리한다.

 

프로세스생성 - 두개 이상의 프로세스가 협조적으로 작업을 해야 할떄는 부모프로세스가 새로운 프로세스를 실행시킬수 있다.

새로 실행된 프로세스는 부모와는 논리적인 연관 없이 독립적으로 실행된다. 사실 프로세스끼리는 부모, 자식 관계가 성립되지 않으며  상호평등한 관계이되 실행을 시작시킨 프로세스를 관행상 부모프로세스라고 부를뿐이다. 프로세스끼리는 임의의 프로세스를 제어할수 있다. 단 이렇게 하기 위해서는 해당 프로세스에 대한 객체를 먼저 구해야 한다.

ex.

 Process Proc = Process.Start("notepad.exe");
 Thread.Sleep(2000);
  Proc.Kill();//그냥 죽이는 것

//CloseMainWindow();  정상종료를 종용하는 것

 

프로세스열거

ex

  Process[] Procs = Process.GetProcesses();
            foreach (Process p in Procs)
            {
                Console.WriteLine("ID = {0,5}, 이름 = {1}", p.Id, p.ProcessName);
            }

 

AppDomain

Win32환경은 프로세스들이 각각의 최대 4G 주소공간을 가지며 서로 격리되어 있다. 프로세스끼리 사용하는 메모리 공간이 완전히 분리되어 있기때문에 한 프로세스가 실수나 고의로 다른 프로세스에 악영향을 미칠수 없도록 되어 있고 그래서 최악의 경우라도 문제가 된 프로그램만 종료되며 시스템은 안전성을 유지할수 있다.

닷넷의 프로세스 구조도 win32와 마찬가지로 격리에 의해 안전성으 확보하는 방식이되 격리의 단위가 프로세스가 아니라 AppDomain(앱도매인)이다. 닷넷은 프로세스를 여러개의 앱도메인으로 분할하며 엡도메인은 닷넷에서 실행코드의 논리적인 경계가 된다. 즉, 한 AppDomain에서 문제가 발생해도 다른 앱도메인의 코드에는 영향을 줄수 없도록 되어 있다. 앱도메인은 안잔성을 위한 장치일뿐 아니라 보안 및 버전 관리, 어셈블리 로드/언로드를 위한 역할을 하기도 한다.

앱도메인에 의한 격리에도 프로세스 주소공간의 격리에서와 마찬가리조 통신의 불편함이 따르는데 앱도메인상의 응용프로그램끼리는 통상적인 방법으로 통신할수 없고 닷넷리모팅 통해서만 통신할수 있다. 프로토콜을

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 8. 28. 12:03
| 1 2 3 4 |