반응형

마이크로소프트의 윈도우 프리젠테이션 파운데이션(WPF)으로 만든 애플리케이션은 Application과 Window 타입의 객체를 생성하는 것으로 시작한다.

간단한 WPF 프로그램 예제.

 

WPF 프로그램에서는 [STAThread] 속성(attribute)이 반드시 Main의 앞에 있어야 한다. 그렇지 않으면 InvalidOperationException 예외 상황과 함께 프로그램이 종료된다.

이 속성은 최초의 애플리케이션 스레드의 스레드 모델을 단일 스레드 아파트먼트(single-threaded apartment)로 지정하는 것을 의미한다. 이는 컴포넌트 오브젝트 모델(COM)과 상호 운용하기 위해 필요하다. 단일 스레드 아파트먼트는 .NET 이전의 과거 COM 시대의 프로그래밍 용어이긴 하지만 애플리케이션이 다중 스레드를 사용하지 않는다는 것을 의미한다고 짐작할 수 있다.

 

SayHello 프로그램에서 Main은 Window 타입의 객체를 생성하는 것으로 시작한다. 이 클래스는 표준 애플리케이션 창을 생성할 때 사용한다. Title 프로퍼티는 생성된 창의 제목 표시줄에 나타나게 될 글자를 지정하며, Show 메소드는 화면에 창을 나타나게 한다.

 

마지막으로 중요한 단계는  Application 객체를 생성하고 Run 메소드를 호출하는 것이다. 윈도우 프로그래밍 용어로 말하면 이 메소드는 메시지 루프를 생성한다고 표현한다. 애플리케이션은 메시지 루프를 통해 키보드나 마우스로 사용자 입력을 받을 수 있게 된다. 태블릿 PC에서 프로그램을 실행한다면 스타일러스 펜의 입력도 받을 수 있다.

 

한 프로그램은 하나의 Application 객체만 생성할 수 있는데, 이 객체는 프로그램 내의 다른 부분에서도 항상 접근할 수 있다. Application 객체는 보이지 않는 반면에, Window 객체는 표준 창으로서 화면에 표시된다.

 

Title 프로퍼티로 지정한 텍스트를 표시하는 제목 표시줄이 있으며, 제목 표시줄의 왼쪽에는 시스템 메뉴 아이콘이 있고, 오른쪽에는 최소화/최대화/닫기 아이콘이 있다. 또한 창에는 창의 크기를 조절하는 경계가 있고, 창 내부를 차지하는 클라이언트 영역이 있다.

 

제한된 범위 내에서 SayHello 프로그램의 Main 메소드 안에 있는 명령문들의 순서를 뒤바꿀 수 있다. 그래도 여전히 제대로 동작할 것이다. 예를 들어 Show 메소드를 호출한 후에 Title 프로퍼티를 설정할 수도 있다. 이론상으로는 이렇게 수정할 경우에 제목 표시줄에 아무런 글자도 없는 상태로 윈도우가 시작되겠지만, 실제로는 너무 빨라서 눈치 챌 수 없을 것이다.

 

Window 객체를 생성하기 전에 Application 객체를 생성할 수도 있다. 그러나 Run 메소드는 반드시 가장 나중에 호출해야 한다. Run 메소드는 창이 닫히기 전까지는 반환되지 않는다. 따라서 창이 닫힐 때 Main 메소드가 종료되고, 창이 정리돼야 한다. Run을 호출하는 부분을 삭제해도 Window 객체는 여전히 생성되고 화면에 창도 표시된다. 그러나 Main이 종료되는 시점에 바로 객체가 소멸된다.

 

Window 객체의 Show 메소드를 호출하는 대신에 아래와 같이 Window 객체를 Run의 인자로 넘길 수도 있다.

app.Run(win);

 

프로그램은 Run 메소드가 호출되기 전까지는 실제로 시작되지 않으며, 호출된 후에야 Window 객체가 사용자의 입력에 반응할 수 있게 된다. 사용자가 창을 닫고 Run 메소드가 반환될 때 프로그램은 종료를 준비한다. 즉, 프로그램은 Run을 호출하는 데 대부분의 시간을 사용한다. 그런데 프로그램이 Run에 모든 시간을 쓴다면 다른 일을 어떻게 할 수 있을까?

 

[SayHello.cs 파일]

using System;
using System.Windows;

namespace SayHello
{
    public class SayHello
    {
        [STAThread]
        public static void Main()
        {
            Window win = new Window();
            win.Title = "Say Hello";
            win.Show();

            Application app = new Application();
            app.Run();
            //app.Run(win);
        }
    }
}

 

프로젝트 다운로드

SayHello.zip
0.02MB

반응형
반응형

찰스 페졸드의 WPF 를 읽으면서 정리한 폰트 선택 다이얼로그 박스(ChooseFont) 소스.

WPF에서 기본으로 제공되지 않는 폰트 선택 다이얼로그 박스를 구현한 소스.

이 소스를 기반으로 좀 더 미려한 폰트 선택 다이얼로그 박스를 만들 수 있을 것이다.

 

[ChooseFont.cs 파일]

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace ChooseFont
{
    public class ChooseFont : Window
    {
        [STAThread]
        public static void Main()
        {
            Application app = new Application();
            app.Run(new ChooseFont());
        }

        public ChooseFont()
        {
            Title = "Choose Font";

            Button btn = new Button();
            btn.Content = Title;
            btn.HorizontalAlignment = HorizontalAlignment.Center;
            btn.VerticalAlignment = VerticalAlignment.Center;
            btn.Click += ButtonOnClick;
            Content = btn;
        }

        void ButtonOnClick(object sender, RoutedEventArgs args)
        {
            FontDialog dlg = new FontDialog();
            dlg.Owner = this;

            //윈도우의 폰트 대화상자 프로퍼티를 설정
            dlg.Typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
            dlg.FontSize = FontSize;

            if (dlg.ShowDialog().GetValueOrDefault())
            {
                //폰트 대화상자에서 윈도우 프로퍼티를 설정
                FontFamily = dlg.Typeface.FontFamily;
                FontStyle = dlg.Typeface.Style;
                FontWeight = dlg.Typeface.Weight;
                FontStretch = dlg.Typeface.Stretch;
                FontSize = dlg.FaceSize;
            }
        }
    }
}

 

[ FontDialog.cs 파일]

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace ChooseFont
{
    public class FontDialog : Window
    {
        TextBoxWithLister boxFamily, boxStyle, boxWeight, boxStretch, boxSize;
        Label lblDisplay;
        bool isUpdateSuppressed = true;

        //Public 프로퍼티
        public Typeface Typeface
        {
            set
            {
                if (boxFamily.Contains(value.FontFamily))
                    boxFamily.SelectedItem = value.FontFamily;
                else
                    boxFamily.SelectedIndex = 0;

                if (boxStyle.Contains(value.Style))
                    boxStyle.SelectedItem = value.Style;
                else
                    boxStyle.SelectedIndex = 0;

                if (boxWeight.Contains(value.Weight))
                    boxWeight.SelectedItem = value.Weight;
                else
                    boxWeight.SelectedIndex = 0;

                if (boxStretch.Contains(value.Stretch))
                    boxStretch.SelectedItem = value.Stretch;
                else
                    boxStretch.SelectedIndex = 0;
            }
            get
            {
                return new Typeface((FontFamily)boxFamily.SelectedItem, (FontStyle)boxStyle.SelectedItem,
                                    (FontWeight)boxWeight.SelectedItem, (FontStretch)boxStretch.SelectedItem);
            }
        }
        public double FaceSize
        {
            set
            {
                double size = 0.75 * value;
                boxSize.Text = size.ToString();

                if (!boxSize.Contains(size)) boxSize.Insert(0, size);

                boxSize.SelectedItem = size;
            }
            get
            {
                double size;

                if (!Double.TryParse(boxSize.Text, out size)) size = 8.25;

                return size / 0.75;
            }
        }

        //생성자
        public FontDialog()
        {
            Title = "Font";
            ShowInTaskbar = false;
            WindowStyle = WindowStyle.ToolWindow;
            WindowStartupLocation = WindowStartupLocation.CenterOwner;
            SizeToContent = SizeToContent.WidthAndHeight;
            ResizeMode = ResizeMode.NoResize;

            //윈도우 Content를 위해 3개 행을 가진 Grid를 생성
            Grid gridMain = new Grid();
            Content = gridMain;

            //TextBoxWithLister 컨트롤을 위한 행
            RowDefinition rowDef = new RowDefinition();
            rowDef.Height = new GridLength(200, GridUnitType.Pixel);
            gridMain.RowDefinitions.Add(rowDef);

            //샘플 텍스트를 위한 행
            rowDef = new RowDefinition();
            rowDef.Height = new GridLength(150, GridUnitType.Pixel);
            gridMain.RowDefinitions.Add(rowDef);

            //버튼을 위한 행
            rowDef = new RowDefinition();
            rowDef.Height = GridLength.Auto;
            gridMain.RowDefinitions.Add(rowDef);

            //메인 Grid를 위한 행
            ColumnDefinition colDef = new ColumnDefinition();
            colDef.Width = new GridLength(650, GridUnitType.Pixel);
            gridMain.ColumnDefinitions.Add(colDef);

            //TextBoxWithLister 컨트롤을 위해 2개 행과 5개 열을 가진 Grid를 생성
            Grid gridBoxes = new Grid();
            gridMain.Children.Add(gridBoxes);

            //라벨을 위한 행
            rowDef = new RowDefinition();
            rowDef.Height = GridLength.Auto;
            gridBoxes.RowDefinitions.Add(rowDef);

            //EditBoxWithLister 컨트롤을 위한 행
            rowDef = new RowDefinition();
            rowDef.Height = new GridLength(100, GridUnitType.Star);
            gridBoxes.RowDefinitions.Add(rowDef);

            //폰트 패밀리를 위한 첫 번째 열
            colDef = new ColumnDefinition();
            colDef.Width = new GridLength(175, GridUnitType.Star);
            gridBoxes.ColumnDefinitions.Add(colDef);

            //폰트 스타일을 위한 두 번째 열
            colDef = new ColumnDefinition();
            colDef.Width = new GridLength(100, GridUnitType.Star);
            gridBoxes.ColumnDefinitions.Add(colDef);

            //폰트 웨이트를 위한 세 번째 열
            colDef = new ColumnDefinition();
            colDef.Width = new GridLength(175, GridUnitType.Star);
            gridBoxes.ColumnDefinitions.Add(colDef);

            //폰트 스트레치를 위한 네 번째 열
            colDef = new ColumnDefinition();
            colDef.Width = new GridLength(100, GridUnitType.Star);
            gridBoxes.ColumnDefinitions.Add(colDef);

            //크기를 위한 다섯 번째 열
            colDef = new ColumnDefinition();
            colDef.Width = new GridLength(75, GridUnitType.Star);
            gridBoxes.ColumnDefinitions.Add(colDef);

            //TextBoxWithLister 컨트롤과 폰트 패밀리 레이블 생성
            Label lbl = new Label();
            lbl.Content = "Font Family";
            lbl.Margin = new Thickness(12, 12, 12, 0);
            gridBoxes.Children.Add(lbl);
            Grid.SetRow(lbl, 0);
            Grid.SetColumn(lbl, 0);

            boxFamily = new TextBoxWithLister();
            boxFamily.IsReadOnly = true;
            boxFamily.Margin = new Thickness(12, 0, 12, 12);
            gridBoxes.Children.Add(boxFamily);
            Grid.SetRow(boxFamily, 1);
            Grid.SetColumn(boxFamily, 0);

            //TextBoxWithLister 컨트롤과 폰트 스타일 레이블 생성
            lbl = new Label();
            lbl.Content = "Style";
            lbl.Margin = new Thickness(12, 12, 12, 0);
            gridBoxes.Children.Add(lbl);
            Grid.SetRow(lbl, 0);
            Grid.SetColumn(lbl, 1);

            boxStyle = new TextBoxWithLister();
            boxStyle.IsReadOnly = true;
            boxStyle.Margin = new Thickness(12, 0, 12, 12);
            gridBoxes.Children.Add(boxStyle);
            Grid.SetRow(boxStyle, 1);
            Grid.SetColumn(boxStyle, 1);

            //TextBoxWithLister 컨트롤과 폰트 웨이트 레이블 생성
            lbl = new Label();
            lbl.Content = "Weight";
            lbl.Margin = new Thickness(12, 12, 12, 0);
            gridBoxes.Children.Add(lbl);
            Grid.SetRow(lbl, 0);
            Grid.SetColumn(lbl, 2);

            boxWeight = new TextBoxWithLister();
            boxWeight.IsReadOnly = true;
            boxWeight.Margin = new Thickness(12, 0, 12, 12);
            gridBoxes.Children.Add(boxWeight);
            Grid.SetRow(boxWeight, 1);
            Grid.SetColumn(boxWeight, 2);

            //TextBoxWithLister 컨트롤과 폰트 스트레치 레이블 생성
            lbl = new Label();
            lbl.Content = "Stretch";
            lbl.Margin = new Thickness(12, 12, 12, 0);
            gridBoxes.Children.Add(lbl);
            Grid.SetRow(lbl, 0);
            Grid.SetColumn(lbl, 3);

            boxStretch = new TextBoxWithLister();
            boxStretch.IsReadOnly = true;
            boxStretch.Margin = new Thickness(12, 0, 12, 12);
            gridBoxes.Children.Add(boxStretch);
            Grid.SetRow(boxStretch, 1);
            Grid.SetColumn(boxStretch, 3);

            //TextBoxWithLister 컨트롤과 크기 레이블 생성
            lbl = new Label();
            lbl.Content = "Size";
            lbl.Margin = new Thickness(12, 12, 12, 0);
            gridBoxes.Children.Add(lbl);
            Grid.SetRow(lbl, 0);
            Grid.SetColumn(lbl, 4);

            boxSize = new TextBoxWithLister();
            boxSize.Margin = new Thickness(12, 0, 12, 12);
            gridBoxes.Children.Add(boxSize);
            Grid.SetRow(boxSize, 1);
            Grid.SetColumn(boxSize, 4);

            //샘플 텍스트를 보여주기 위한 레이블 생성
            lblDisplay = new Label();
            lblDisplay.Content = "AaBbCc XxYyZz 012345";
            lblDisplay.HorizontalContentAlignment = HorizontalAlignment.Center;
            lblDisplay.VerticalContentAlignment = VerticalAlignment.Center;
            gridMain.Children.Add(lblDisplay);
            Grid.SetRow(lblDisplay, 1);

            //버튼을 위해 5개의 열을 가진 Grid를 생성
            Grid gridButtons = new Grid();
            gridMain.Children.Add(gridButtons);
            Grid.SetRow(gridButtons, 2);

            for (int i = 0; i < 5; i++)
            {
                gridButtons.ColumnDefinitions.Add(new ColumnDefinition());
            }

            //OK 버튼
            Button btn = new Button();
            btn.Content = "OK";
            btn.IsDefault = true;
            btn.HorizontalAlignment = HorizontalAlignment.Center;
            btn.MinWidth = 60;
            btn.Margin = new Thickness(12);
            btn.Click += OkOnClick;
            gridButtons.Children.Add(btn);
            Grid.SetColumn(btn, 1);

            //Cancel 버튼
            btn = new Button();
            btn.Content = "Cancel";
            btn.IsCancel = true;
            btn.HorizontalAlignment = HorizontalAlignment.Center;
            btn.MinWidth = 60;
            btn.Margin = new Thickness(12);
            gridButtons.Children.Add(btn);
            Grid.SetColumn(btn, 3);

            //시스템 폰트 패밀리로 폰트 패밀리 박스를 초기화
            foreach (FontFamily fam in Fonts.SystemFontFamilies)
            {
                boxFamily.Add(fam);
            }

            //폰트 크기 박스를 초기화
            double[] ptSizes = new double[] { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 };
            foreach (double ptSize in ptSizes)
            {
                boxSize.Add(ptSize);
            }

            //이벤트 핸들러를 설정
            boxFamily.SelectionChanged += FamilyOnSelectionChanged;
            boxStyle.SelectionChanged += StyleOnSelectionChanged;
            boxWeight.SelectionChanged += StyleOnSelectionChanged;
            boxStretch.SelectionChanged += StyleOnSelectionChanged;
            boxSize.TextChanged += SizeOnTextChanged;

            //윈도우 프로퍼티를 기반으로 선택 값을 설정
            //(이 부분은 프로퍼티가 설정되면 오버라이딩됨)
            Typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);

            FaceSize = FontSize;

            //키보드 포커스를 설정
            boxFamily.Focus();

            //샘플 텍스트를 수정할 수 있게 함
            isUpdateSuppressed = false;
            UpdateSample();
        }

        //폰트 패밀리 박스에 대한 SelectionChanged 이벤트 핸들러
        void FamilyOnSelectionChanged(object sender, EventArgs args)
        {
            //선택한 패밀리를 구함
            FontFamily fontFamily = (FontFamily)boxFamily.SelectedItem;

            //이전 스타일, 웨이트, 스트레치를 저장
            //이 값은 이 메소드가 처음 불릴 때는 null
            FontStyle? fntStyPrevious = (FontStyle?) boxStyle.SelectedItem;
            FontWeight? fntWtPrevious = (FontWeight?) boxWeight.SelectedItem;
            FontStretch? fntStrPrevious = (FontStretch?) boxStretch.SelectedItem;

            //샘플이 보이지 않게 함
            isUpdateSuppressed = true;

            //스타일, 웨이트, 스트레치 박스를 지움
            boxStyle.Clear();
            boxWeight.Clear();
            boxStretch.Clear();

            //선택된 폰트 패밀리의 typefaces에 대해서 루프를 수행
            foreach (FamilyTypeface ftf in fontFamily.FamilyTypefaces)
            {
                //boxStyle에 스타일을 추가(Normal이 가장 상위에 위치)
                if (!boxStyle.Contains(ftf.Style))
                {
                    if (ftf.Style == FontStyles.Normal)
                        boxStyle.Insert(0, ftf.Style);
                    else
                        boxStyle.Add(ftf.Style);
                }
                //boxWeight에 웨이트를 추가(Normal이 가장 상위에 위치)
                if (!boxWeight.Contains(ftf.Weight))
                {
                    if (ftf.Weight == FontWeights.Normal)
                        boxWeight.Insert(0, ftf.Weight);
                    else
                        boxWeight.Add(ftf.Weight);
                }
                //boxStretch에 스트레치를 추가(Normal이 가장 상위에 위치)
                if (!boxStretch.Contains(ftf.Stretch))
                {
                    if (ftf.Stretch == FontStretches.Normal)
                        boxStretch.Insert(0, ftf.Stretch);
                    else
                        boxStretch.Add(ftf.Stretch);
                }
            }

            //boxStyle에 선택 항목을 설정
            if (boxStyle.Contains(fntStyPrevious))
                boxStyle.SelectedItem = fntStyPrevious;
            else
                boxStyle.SelectedIndex = 0;

            //boxWeight에 선택 항목을 설정
            if (boxWeight.Contains(fntWtPrevious))
                boxWeight.SelectedItem = fntWtPrevious;
            else
                boxWeight.SelectedIndex = 0;

            //boxStretch에 선택 항목을 설정
            if (boxStretch.Contains(fntStrPrevious))
                boxStretch.SelectedItem = fntStrPrevious;
            else
                boxStretch.SelectedIndex = 0;

            //샘플 수정이 가능하게 하고 샘플을 갱신
            isUpdateSuppressed = false;
            UpdateSample();
        }

        //스타일, 웨이트, 스트레치 박스에 대한 SelectionChanged 이벤트 핸들러
        void StyleOnSelectionChanged(object sender, EventArgs args)
        {
            UpdateSample();
        }

        //크기 박스에 대한 TextChanged 이벤트 핸들러
        void SizeOnTextChanged(object sender, TextChangedEventArgs args)
        {
            UpdateSample();
        }

        //샘플 텍스트 갱신
        void UpdateSample()
        {
            if (isUpdateSuppressed) return;

            lblDisplay.FontFamily = (FontFamily)boxFamily.SelectedItem;
            lblDisplay.FontStyle = (FontStyle)boxStyle.SelectedItem;
            lblDisplay.FontWeight = (FontWeight)boxWeight.SelectedItem;
            lblDisplay.FontStretch = (FontStretch)boxStretch.SelectedItem;

            double size;

            if (!Double.TryParse(boxSize.Text, out size))
                size = 8.25;

            lblDisplay.FontSize = size / 0.75;
        }

        //OK 버튼을 누르면 대화상자를 종료
        void OkOnClick(object sender, RoutedEventArgs args)
        {
            DialogResult = true;
        }
    }
}

 

[TextBoxWithLister.cs 파일]

using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace ChooseFont
{
    public class TextBoxWithLister : ContentControl
    {
        TextBox txtBox;
        Lister lister;
        bool isReadOnly;

        //Public 이벤트
        public event EventHandler SelectionChanged;
        public event TextChangedEventHandler TextChanged;

        //생성자
        public TextBoxWithLister()
        {
            //윈도우 Content를 위해 DockPanel 생성
            DockPanel dock = new DockPanel();
            Content = dock;

            //상단에 위치할 텍스트 박스
            txtBox = new TextBox();
            txtBox.TextChanged += TextBoxOnTextChanged;
            dock.Children.Add(txtBox);
            DockPanel.SetDock(txtBox, Dock.Top);

            //DockPanel의 나머지에 Lister를 추가
            lister = new Lister();
            lister.SelectionChanged += ListerOnSelectionChanged;
            dock.Children.Add(lister);
        }

        //텍스트 박스 항목과 관련된 Public 프로퍼티
        public string Text
        {
            get { return txtBox.Text; }
            set { txtBox.Text = value; }
        }
        public bool IsReadOnly
        {
            get { return isReadOnly; }
            set { isReadOnly = value; }
        }

        //Lister 요소의 다른 public 프로퍼티 인터페이스
        public object SelectedItem
        {
            set
            {
                lister.SelectedItem = value;

                if (lister.SelectedItem != null)
                    txtBox.Text = lister.SelectedItem.ToString();
                else
                    txtBox.Text = "";
            }
            get
            {
                return lister.SelectedItem;
            }
        }
        public int SelectedIndex
        {
            set
            {
                lister.SelectedIndex = value;

                if (lister.SelectedIndex == -1)
                    txtBox.Text = "";
                else
                    txtBox.Text = lister.SelectedItem.ToString();
            }
            get
            {
                return lister.SelectedIndex;
            }
        }
        public void Add(object obj)
        {
            lister.Add(obj);
        }
        public void Insert(int index, object obj)
        {
            lister.Insert(index, obj);
        }
        public void Clear()
        {
            lister.Clear();
        }
        public bool Contains(object obj)
        {
            return lister.Contains(obj);
        }

        //마우스를 클릭하면 키보드 포커스를 설정
        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            base.OnMouseDown(e);
            Focus();
        }

        //키보드가 포커스를 갖게 되면 텍스트 박스에 포커스를 설정
        protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
        {
            base.OnGotKeyboardFocus(e);

            if (e.NewFocus == this)
            {
                txtBox.Focus();
                if (SelectedIndex == -1 && lister.Count > 0)
                    SelectedIndex = 0;
            }
        }

        //첫 문자를 입력하면 이 값을 GoToLetter 메소드로 넘김
        protected override void OnPreviewTextInput(TextCompositionEventArgs e)
        {
            base.OnPreviewTextInput(e);

            if (IsReadOnly)
            {
                lister.GoToLetter(e.Text[0]);
                e.Handled = true;
            }
        }

        //선택 항목을 변경하기 위해 커서 이동키를 처리
        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            base.OnPreviewKeyDown(e);

            if (SelectedIndex == -1) return;

            switch(e.Key)
            {
                case Key.Home:
                    if (lister.Count > 0) SelectedIndex = 0;
                    break;
                case Key.End:
                    if (lister.Count > 0) SelectedIndex = lister.Count - 1;
                    break;
                case Key.Up:
                    if (SelectedIndex > 0) SelectedIndex--;
                    break;
                case Key.Down:
                    if (SelectedIndex < lister.Count - 1) SelectedIndex++;
                    break;
                case Key.PageUp:
                    lister.PageUp();
                    break;
                case Key.PageDown:
                    lister.PageDown();
                    break;
                default:
                    return;
            }
            e.Handled = true;
        }

        //이벤트 핸들러와 트리거
        void ListerOnSelectionChanged(object sender, EventArgs args)
        {
            if (SelectedIndex == -1)
                txtBox.Text = "";
            else
                txtBox.Text = lister.SelectedItem.ToString();

            OnSelectionChanged(args);
        }

        void TextBoxOnTextChanged(object sender, TextChangedEventArgs args)
        {
            if (TextChanged != null) TextChanged(this, args);
        }
        protected virtual void OnSelectionChanged(EventArgs args)
        {
            if (SelectionChanged != null) SelectionChanged(this, args);
        }
    }
}

 

[Lister.cs 파일]

using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace ChooseFont
{
    public class Lister : ContentControl
    {
        ScrollViewer scroll;
        StackPanel stack;
        ArrayList list = new ArrayList();
        int indexSelected = -1;

        //Public 이벤트
        public event EventHandler SelectionChanged;

        //생성자
        public Lister()
        {
            Focusable = false;

            //윈도우의 Content를 ContentControl의 Border로 설정
            Border border = new Border();
            border.BorderThickness = new Thickness(1);
            border.BorderBrush = SystemColors.ActiveBorderBrush;
            border.Background = SystemColors.WindowBrush;
            Content = border;

            //border의 자식으로 ScrollViewer 생성
            scroll = new ScrollViewer();
            scroll.Focusable = false;
            scroll.Padding = new Thickness(2, 0, 0, 0);
            border.Child = scroll;

            //ScrollViewer의 Content로 스택 패널을 생성
            stack = new StackPanel();
            scroll.Content = stack;

            //마우스 왼쪽 버튼에 대한 핸들러를 연결
            AddHandler(TextBlock.MouseLeftButtonDownEvent, new MouseButtonEventHandler(TextBlockOnMouseLeftButtonDown));

            Loaded += OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            //Lister가 처음 보여질 때 뷰에 선택된 항목을 스크롤
            ScrollIntoView();
        }

        //Lister에 항목을 추가하고, 삽입하는 Public 메소드
        public void Add(object obj)
        {
            list.Add(obj);
            TextBlock txtBlk = new TextBlock();
            txtBlk.Text = obj.ToString();
            stack.Children.Add(txtBlk);
        }

        public void Insert(int index, object obj)
        {
            list.Insert(index, obj);
            TextBlock txtBlk = new TextBlock();
            txtBlk.Text = obj.ToString();
            stack.Children.Insert(index, txtBlk);
        }

        public void Clear()
        {
            SelectedIndex = -1;
            stack.Children.Clear();
            list.Clear();
        }

        public bool Contains(object obj)
        {
            return list.Contains(obj);
        }

        public int Count
        {
            get { return list.Count; }
        }

        //입력한 문자에 따라 항목이 선택되게 하기 위해 호출되는 메소드
        public void GoToLetter(char ch)
        {
            int offset = SelectedIndex + 1;

            for (int i = 0; i < Count; i++)
            {
                int index = (i + offset) % Count;

                if (Char.ToUpper(ch) == Char.ToUpper(list[index].ToString()[0]))
                {
                    SelectedIndex = index;
                    break;
                }
            }
        }

        //선택바를 출력하기 위한 SelectedIndex 프로퍼티
        public int SelectedIndex
        {
            set
            {
                if (value < -1 || value >= Count)
                    throw new ArgumentOutOfRangeException("SelectedIndex");

                if (value == indexSelected) return;

                if (indexSelected != -1)
                {
                    TextBlock txtBlk = stack.Children[indexSelected] as TextBlock;
                    txtBlk.Background = SystemColors.WindowBrush;
                    txtBlk.Foreground = SystemColors.WindowTextBrush;
                }

                indexSelected = value;

                if (indexSelected > -1)
                {
                    TextBlock txtBlk = stack.Children[indexSelected] as TextBlock;
                    txtBlk.Background = SystemColors.HighlightBrush;
                    txtBlk.Foreground = SystemColors.HighlightTextBrush;
                }
                ScrollIntoView();

                //SelectionChanged 이벤트 트리거
                OnSelectionChanged(EventArgs.Empty);
            }
            get
            {
                return indexSelected;
            }
        }

        //SelectionItem 프로퍼티는 SelectedIndex를 이용
        public object SelectedItem
        {
            set
            {
                SelectedIndex = list.IndexOf(value);
            }
            get
            {
                if (SelectedIndex > -1) return list[SelectedIndex];
                return null;
            }
        }

        //리스트에서 페이지 업, 페이지 다운하는 Public 메소드
        public void PageUp()
        {
            if (SelectedIndex == -1 || Count == 0) return;

            int index = SelectedIndex - (int)(Count * scroll.ViewportHeight / scroll.ExtentHeight);
            if (index < 0) index = 0;

            SelectedIndex = index;
        }

        public void PageDown()
        {
            if (SelectedIndex == -1 || Count == 0) return;

            int index = SelectedIndex + (int)(Count * scroll.ViewportHeight / scroll.ExtentHeight);
            if (index > Count - 1) index = Count - 1;

            SelectedIndex = index;
        }

        //뷰에서 선택 항목을 스크롤하는 Private 메소드
        void ScrollIntoView()
        {
            if (Count == 0 || SelectedIndex == -1 || scroll.ViewportHeight > scroll.ExtentHeight) return;

            double heightPerItem = scroll.ExtentHeight / Count;
            double offsetItemTop = SelectedIndex * heightPerItem;
            double offsetItemBot = (SelectedIndex + 1) * heightPerItem;

            if (offsetItemTop < scroll.VerticalOffset) scroll.ScrollToVerticalOffset(offsetItemTop);
            else if (offsetItemBot > scroll.VerticalOffset + scroll.ViewportHeight)
            {
                scroll.ScrollToVerticalOffset(scroll.VerticalOffset + offsetItemBot - scroll.VerticalOffset - scroll.ViewportHeight);
            }
        }

        //이벤트 핸들러와 트리거
        void TextBlockOnMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
        {
            if (args.Source is TextBlock)
                SelectedIndex = stack.Children.IndexOf(args.Source as TextBlock);
        }

        protected virtual void OnSelectionChanged(EventArgs args)
        {
            if (SelectionChanged != null)
                SelectionChanged(this, args);
        }
    }
}

 

반응형
반응형

찰스 페졸드의 WPF 를 읽으면서 정리한 Banner 인쇄(PrintBanner) 소스.

 

[PrintBanner.cs 파일]

using System;
using System.Printing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace PrintBanner
{
    public class PrintBanner : Window
    {
        TextBox txtBox;

        [STAThread]
        public static void Main()
        {
            Application app = new Application();
            app.Run(new PrintBanner());
        }

        public PrintBanner()
        {
            Title = "Print Banner";
            SizeToContent = SizeToContent.WidthAndHeight;

            //윈도우 Content를 위한 스택 패널 생성
            StackPanel stack = new StackPanel();
            Content = stack;

            //텍스트 박스 생성
            txtBox = new TextBox();
            txtBox.Width = 250;
            txtBox.Margin = new Thickness(12);
            stack.Children.Add(txtBox);

            //버튼 생성
            Button btn = new Button();
            btn.Content = "_Print...";
            btn.Margin = new Thickness(12);
            btn.Click += PrintOnClick;
            btn.HorizontalAlignment = HorizontalAlignment.Center;
            stack.Children.Add(btn);

            txtBox.Focus();
        }

        private void PrintOnClick(object sender, RoutedEventArgs e)
        {
            PrintDialog dlg = new PrintDialog();

            if (dlg.ShowDialog().GetValueOrDefault())
            {
                //인쇄 방향이 수직인지 확인
                PrintTicket prnTkt = dlg.PrintTicket;
                prnTkt.PageOrientation = PageOrientation.Portrait;
                
                dlg.PrintTicket = prnTkt;

                //BannerDocumentPaginator 객체 생성
                BannerDocumentPaginator paginator = new BannerDocumentPaginator();

                //텍스트 박스로 Text 프로퍼티를 설정
                paginator.Text = txtBox.Text;

                //종이의 크기를 기반으로 PageSize 프로퍼티 설정
                paginator.PageSize = new Size(dlg.PrintableAreaWidth, dlg.PrintableAreaHeight);

                //Call PrintDocument to print the document
                dlg.PrintDocument(paginator, "Banner : " + txtBox.Text);
            }
        }
    }
}

 

[BannerDocumentPaginator.cs 파일]

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

namespace PrintBanner
{
    public class BannerDocumentPaginator : DocumentPaginator
    {
        string txt = "";
        Typeface face = new Typeface("");
        Size sizePage;
        Size sizeMax = new Size(0, 0);

        //이 DocumentPaginator에 특화된 Public 프로퍼티
        public string Text
        {
            get { return txt; }
            set { txt = value; }
        }

        public Typeface Typeface
        {
            get { return face; }
            set { face = value; }
        }
        
        //FormattedText 객체를 생성하는 Private 함수
        FormattedText GetFormattedText(char ch, Typeface face, double em)
        {
            return new FormattedText(ch.ToString(), CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
                                    face, em, Brushes.Black);
        }

        //오버라이딩이 필요
        public override bool IsPageCountValid
        {
            get
            {
                //100em 사이즈를 기반으로 문자의 최대 크기를 결정
                foreach (char ch in txt)
                {
                    FormattedText formTxt = GetFormattedText(ch, face, 100);
                    sizeMax.Width = Math.Max(sizeMax.Width, formTxt.Width);
                    sizeMax.Height = Math.Max(sizeMax.Height, formTxt.Height);
                }
                return true;
            }
        }

        public override int PageCount
        {
            get { return txt == null ? 0 : txt.Length; }
        }

        public override Size PageSize
        {
            get { return sizePage; }
            set { sizePage = value; }
        }

        public override DocumentPage GetPage(int numPage)
        {
            DrawingVisual vis = new DrawingVisual();
            DrawingContext dc = vis.RenderOpen();

            //em 사이즈의 factor를 계산할 때 1/2인치 여백을 가정
            double factor = Math.Min((PageSize.Width - 96) / sizeMax.Width,
                                    (PageSize.Height - 96) / sizeMax.Height);

            FormattedText formTxt = GetFormattedText(txt[numPage], face, factor * 100);

            //페이지 중앙에 위치할 수 있게 좌표 계산
            Point ptText = new Point((PageSize.Width - formTxt.Width) / 2,
                                    (PageSize.Height - formTxt.Height) / 2);

            dc.DrawText(formTxt, ptText);
            dc.Close();

            return new DocumentPage(vis);
        }

        public override IDocumentPaginatorSource Source
        {
            get { return null; }
        }
    }
}

 

 

반응형

+ Recent posts