WPF 팁

Form Designer 프로젝트 #7 - Tool Window (콘트롤 속성 지정/변경) - 완료! 안떠니 평점: 9.8/10 (6명 참여) 조회: 4139
이제 겨우 코드를 완성했네요. 기다려주신 분들이 계실지도 모르니, 괜히 미안하네요 ^^v
그럼 시작하겠습니다.

아래 글에서 보여드린 것처럼 동적으로 생성될 수 있는 콘트롤을 몇개로 제한시켰습니다.
지금 이 프로젝트가 상업용도 아니고 단지 간단한 필요에 의해 작성되는 것이기 때문입니다.

제공하는 콘트롤은,

TextBox
Button
ListBox
ComboBox
RadioButton

이며, TextBox 에 기본 속성을 지정해서, 마치 Label 이랑 멀티라인이 가능한 Comment 라는 콘트롤을 제공합니다.
Label, Comment 이 2개는 현재 폼 디자인 기능에 기본적으로 필요해서 넣은 것 뿐이므로, 큰 의미는 없습니다.

이러한 콘트롤에 가장 기본적인 속성, 폰트 종류/크기/색, 배경색을 툴 윈도우에서 제공하려고 합니다.
별도의 속성창 (델파이나 VS 에 있는) 을 제공하지 않고, 매우 단순하게 하려는 의도입니다.

그럼 하나씩 한번 살펴보죠. 먼저, 배경색을 지정하는 방법입니다.
툴 윈도우가 화면에 로드되는 순간에 배경색을 지정할 수 있는 콤보박스를 올려놓습니다.
그리고, 이 콤보박스에 시스템에서 제공하는 색상 목록을 데이터바인딩 시킵니다. 

        <ComboBox Height="23" Margin="86,130,9,0" Name="cbBackColor" VerticalAlignment="Top" SelectionChanged="cbBackColor_Changed">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Rectangle Margin="5,1,10,1" Width="20" Fill="{Binding Name}" />
                        <TextBlock Text="{Binding Name}" />
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>

위와 같이 XAML 파일에서 ComboBox 를 올려놓고 위의 2번째 줄부터는 직접 입력해 넣어야 합니다.
속성창에서 간단히 하는 방법도 있겠지만, 안해봐서 잘모르겠네요. 그래서 일단 패스~
그리고, 툴 윈도우가 로드되는 이벤트에서 아래와 같이 바인딩을 실행합니다.

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.cbBackColor.ItemsSource = typeof(System.Windows.Media.Colors).GetProperties();
            this.cbBackColor.SelectedIndex = IndexofColorList(this.cbBackColor, "White");
        }


그리고, 위에서 툴 윈도우가 화면에 보여질때 배경색 콤보박스에 기본 색상 (흰색) 이 선택되도록 지정합니다.
여기서, 제가 한참을 씨름한 부분이 있습니다.

콥보박스에 데이터를 바인딩하면, 그 속에 있는 값을 찾아서 그 값이 선택된 것으로 지정하는 부분이죠.
여기서는 흰색을 목록에서 찾아서 그 아이템이 콤보박스의 현재값으로 설정이 되게 하는거죠.
그래서, IndexofColorList 라는 함수를 만들었습니다. 콤보박스와 흰색이라는 문자열을 전달해 주면,
콤보박스에서 흰색 문자열을 찾아서, 찾은 시점의 Index 번호를 리턴합니다. 코드는 아래에,

        private int IndexofColorList(ComboBox cb, string ColorName)
        {
            int Result = -1;
            string zColor = "";
            {
                foreach (var item in cb.Items)
                {
                    Result++;
                    zColor = item.ToString().Replace("System.Windows.Media.Color ", "");
                    if (zColor == ColorName) break;
                }
            }
            return Result;
        }


위의 코드을 작성하는데 2곳에서 삽질을 했습니다.
첫번째는 foreach (var item in cb.Items) 입니다. 하위 collection 을 접근하기 위해서는 보통 아래처럼

foreach (ComboBoxItem item in cb.Items)

사용할꺼라고 생각했는데. 이게 아니더군요. 왜 안될까...하고..검색을 해보니,
Items 의 collection 은 객체형 선언없이 그냥 var 변수로 선언해서 사용하는 코드를 발견했습니다.
그랬더니, item 으로 collection 이 접근되더군요.

하지만, 삽질을 하게 된 이유는, 디버깅할 때, foreach 문에서 ComboBoxItem 또는 object 등으로 선언이 된 경우,
아무런 에러도 발생시키지 않고, 그냥 이 함수에서 벗어나고, 그리고, Windows_Loaded 이벤트에서도 벗어나버리는..

이 부분에서, 아 VS/C#/WPF 가 아직 좀 불안하구나...하는 생각을 갖게 되었습니다.
이것 외에도 빌드할때 분명 에러가 있다고 하단에 경고하고 같이 보여졌는데, 실행을 클릭하면, 실행이 그냥 된다는....
그리고, 에러가 있는 부분에 실행이 도달하면, 그때가서 에러를 보여주거나, 그냥 지나쳐~~~버리는....황당한 경우가..

아주 황당했죠...foreach 문에서 Items 의 collection 을 접근하는 방법이 다른 객체와 다르다는...일관성 결여~~
그리고, 문제가 있으면 빌드할 때 에러를 뱉던가...아니면, 실행할때 뱉던가...하지..빌드할때도 지나치고..
실행할 때도 에러 없이...튕겨져 버리고..........멘붕이죠...

두번째 삽질은 콤보박스에 2개의 데이터를 그룹으로 바인딩할 경우, 콤보박스의 값을 참조하는 방법에서 있었습니다.


                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Rectangle Margin="5,1,10,1" Width="20" Fill="{Binding Name}" />
                        <TextBlock Text="{Binding Name}" />
                    </StackPanel>
                </DataTemplate>

위와 같이 2개의 데이터를 Rectangle, TextBlock 의 객체에 바인딩을 했더니,
콤보박스의 현재 선택된 값이 아래처럼 리턴되어 오더군요.

System.Windows.Media.Color White

색상 자체는 위처럼 그룹 이름으로 리턴되고, 색상의 이름은 White 로 리턴되어 두개가 묶여져서 리턴되는거죠.
foreach( var item ........ ) 에서 참조하는 item 을 갖고서 위의 2개 열을 따로 접근할 수 있는지, 확인해 봤습니다.
몇시간, 삽질했지만, 없는 것처럼 보여서, 아니면,...제가 방법을 못찾아서.....일단...패스하고, 문자열 치환으로 해결~

zColor = item.ToString().Replace("System.Windows.Media.Color ", "");

너무 꽁수인가요? 모 여튼...작동 잘하는 관계로~ 패스~~~~
자, 이제는 배경색 콤보박스에서 다른 색을 선택했을 때, 현재 선택된 콘트롤의 배경색을 바꿔주는 부분입니다.

        private void cbBackColor_Changed(object sender, SelectionChangedEventArgs e)
        {
            String zBackColor = "";
UIElement obj = ((MainWindow)this.Owner).canvasDesigner.selectedElem;
            if ((obj != null) &&  (cbBackColor.Text != ""))
            {              
                zBackColor = (sender as ComboBox).SelectedItem.ToString().Replace("System.Windows.Media.Color ", "");
                if (cbBackColor.SelectedItem != null) (obj as Control).Background = new BrushConverter().ConvertFromString(zBackColor) as SolidColorBrush;
            }
        }

위의 이벤트는 cbBackColor 라는 콤보박스의 선언 xaml 파일에서 SelectionChanged 속성에서 지정되었습니다.
여기서, 또 이상한 부분이 나타났습니다. 콤보박스의 Text 는 변경 직전의 값을 갖고 있고, 변경된 값에 접근하려면,

cbBackColor.SelectedItem.ToString()

으로 접근해야 한다는 것입니다. 구글링을 해보니, 이걸 물어본 사람들이 몇명 있더군요.
이게 정상인지...비정상인지는...여러분이 판단하시고, 다른 툴과는 조금 다른 방식이다, 라는 정도로 알아두죠.
여기서도, 색상이 선택되고 나면, 선택된 문자열에서 시스템 문자열을 잘라내고, 실제 색상 이름만을 뽑아서,
그 문자열을 Background 속성에 입력할 수 있도록 Brush 객체로 변환합니다. 위의 코드 중에서,

(obj as Control).Background = new BrushConverter().ConvertFromString(zBackColor) as SolidColorBrush;


계속 이런 식의 코드를 자주 보게 되는데요. 이게 좋은 방식인지..아닌지..아직도 잘 모르겠네요.
C# 의 고유 방식이라고 이해하고 일단 패스~~~~ 개인적으로 이런 방식은 불편하게 느껴지네요.


배경색의 지정과 변경 작업은 이걸루 끝났구요. Foreground 의 색은 배경색과 동일합니다.
단지, 콘트롤의 속성이 Background 가 아닌 Foreground 라는 것만 다르죠. 나중에 전체 소스를 참고하세요.
그래서, 여기서는 생략하고, 다음은 폰트의 크기를 지정하는 콤보박스를 살펴보죠.


폰트의 크기는 임의로 지정해서 아래와 같이 XAML 에서 직접 타이핑 했습니다.

        <ComboBox Height="23"  Margin="173,186,9,0" Name="cbFontSize" VerticalAlignment="Top" SelectionChanged="cbFontSize_Changed">
            <ComboBoxItem Content="8"/>
            <ComboBoxItem Content="9"/>
            <ComboBoxItem Content="10"/>
            <ComboBoxItem Content="11"/>
            <ComboBoxItem Content="12"/>
            <ComboBoxItem Content="14"/>
            <ComboBoxItem Content="18"/>
            <ComboBoxItem Content="20"/>
            <ComboBoxItem Content="24"/>
            <ComboBoxItem Content="40"/>
        </ComboBox>

그리고, 툴 윈도우가 화면에 보여질 때 이 폰트 크기도 기본값 12 로 설정되게 하였습니다. 다음과 같이..

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.cbFontSize.SelectedIndex = 4; // font size is 12 by default.
        }

제가 직접 입력한 관계로, Index 값 4 가 폰트 크기 12 라는 것을 알죠? 그래서, 그냥 4 로 박아 넣었습니다. ^^V
그리고, 특정 콘트롤이 선택되었을 때, 폰트 크기를 변경하면, 변경 내용을 그 콘트롤에 전달해줘야겠죠.

        private void cbFontSize_Changed(object sender, SelectionChangedEventArgs e)
        {
            String zFontSize = "";
            UIElement obj = ((MainWindow)this.Owner).canvasDesigner.selectedElem;
            if ((obj != null) && (cbFontSize.Text != ""))
            {
                zFontSize = (sender as ComboBox).SelectedItem.ToString().Replace("System.Windows.Controls.ComboBoxItem: ", "");
                (obj as Control).FontSize = Convert.ToDouble(zFontSize);
            }
        }

여기서, 또 이상한 부분이 보이네요. SelectedItem.ToString() 을 하면, 그냥 문자열만 리턴되는게 맞을 것 같은데,
콤보박스의 아이템 객체형의 이름을 같이 묶어서 리턴하네요...정말...멘붕~~~~
이따가 아래에서 얘기하겠지만, FontFamily 의 경우는 이름만 리턴합니다. 폰
트 패밀리의 경우, 물론 시스템 데이터와 바인딩을 했지만, 도대체 무슨 차이로 이렇게 달라지는지...몰겠네요. 멘붕~

이젠, 폰트의 패밀리를 지정하는 방법입니다.
폰트 패밀리도 배경색과 마찬가지로 시스템에서 제공하는 데이터를 가져와서 바인딩했습니다.

<ComboBox Height="23"  Margin="86,186,47,0" Name="cbFontFamily" VerticalAlignment="Top" ItemsSource="{x:Static Fonts.SystemFontFamilies}" SelectionChanged="cbFontFamily_Changed" />

Fonts.SystemFontFamilies 의 경우는 이름만을 리턴하기 때문에, 배경색과는 달리 XAML 에서 DataTemplate 를 정의하지 않았습니다. 간단하게 되니까...이건 좋네요.

그리고, 툴 윈도우가 화면에 보여질때 기본 폰트 패밀리를 지정하기 위해 아래와 같이 코딩을 합니다.

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.cbFontFamily.SelectedIndex = IndexofFontList(this.cbFontFamily, "Arial");
        }

기본 폰트는 Arial 로 지정했구요. 배경색과 마찬가지로 폰트 이름을 갖고서 콤보박스에 위치를 찾는 함수를 만들었습니다. 아래는 IndexofFontList 입니다. 콤보박스와 폰트 이름을 인자로 전달하면, 콤보박스에서 Index 를 리턴합니다.
        private int IndexofFontList(ComboBox cb, string FontFamily)
        {
            int Result = -1;
            string zFont = "";
            {
                foreach (var item in cb.Items)
                {
                    Result++;
                    zFont = item.ToString();
                    if (zFont == FontFamily) break;
                }
            }
            return Result;
        }


위의 배경색과 폰트 크기의 설정하는 것과 마찬가지로, 폰트 패밀리가 변경되면, 현재 포커스가 있는 콘트롤의 폰트를 변경시킵니다.

        private void cbFontFamily_Changed(object sender, SelectionChangedEventArgs e)
        {
            String zFontFamily = "";
            UIElement obj = ((MainWindow)this.Owner).canvasDesigner.selectedElem;
            if ((obj != null) && (cbFontFamily.Text != ""))
            {
                zFontFamily = (sender as ComboBox).SelectedItem.ToString();
                (obj as Control).FontFamily = new FontFamily(zFontFamily);
            }
        }


여기서도 Brush 를 생성해서 배경색/글자색을 지정한 것처럼 폰트 패밀리도 FontFamily 라는 객체의 인스턴스를 생성시켜 폰트 패밀리를 지정합니다. 이게 좋은 방식인지..아닌지는 각자가 알아서...저는 불편하네요 ^^;
개발자를 귀찮게 하는 것처럼 보여서요 ㅎㅎㅎㅎ


이제 마지막으로 콘트롤에 텍스트를 입력하는 작업입니다.
모든 콘트롤이 텍스트를 입력받는 것이 아니고, 버튼, 텍스트박스 등이 콘트롤 위에 글자를 표시할 수 있습니다.
리스트박스, 콤보박스는 아이템을 추가해야 하는 구조이기 때문에 여기에서는 제외를 시켰습니다.
아이템 추가/삭제하는 기능까지 넣으면, 규모가 너무 커져가는 ...ㅠㅠㅠ 그래서 패스~~~

텍스트 박스는 XAML 에서 아래와 같이 정의되었습니다.

        <TextBox HorizontalAlignment="Left"  Height="40" Margin="11,223,0,0" TextWrapping="Wrap" AcceptsReturn="True"
                 Text="Type text of control here..."
                 TextChanged="TextChangedatObject"
                 VerticalAlignment="Top" Width="201"/>

TextChanged 이벤트를 추가해서, 글자가 입력될 때마다 그 값들을 실시간으로 바로바로 콘트롤에 전달하는거죠.
그러면, TextChanged 이벤트는 이렇게 코딩되겠죠.

        private void TextChangedatObject(object sender, TextChangedEventArgs e)
        {
            if ((MainWindow)this.Owner != null)
            {
                UIElement obj = ((MainWindow)this.Owner).canvasDesigner.selectedElem;
                if (obj != null)
                {
                    if (obj is TextBox) (obj as TextBox).Text = (sender as TextBox).Text;
                    if (obj is Button) (obj as Button).Content = (sender as TextBox).Text;
                    if (obj is CheckBox) (obj as CheckBox).Content = (sender as TextBox).Text;
                    if (obj is RadioButton) (obj as RadioButton).Content = (sender as TextBox).Text;                       
                }
            }
        }

위의 코드를 좀 최적화하고 싶은데....객체형을 각각 확인하지 않고, 일괄적으로 값을 대입하는거죠.
근데 각 콘트롤마다 텍스트 속성의 이름이 다르니....참...일관성도 없게 만들었네요. 또 멘붕~~~

자...

이렇게 해서, 툴 윈도우에서 콘트롤의 기본 속성을 지정하는 코딩 부분을 마쳤습니다.
한가지 속성 지정 부분에서 아쉽고 빠진 부분은 1개 이상의 콘트롤이 묶음으로 선택된 경우의 속성 변경입니다.
가령, Label 을 여러개 선택한 다음 폰트 배경색을 바꾸고 싶다. 이런 경우의 처리가 빠졌죠.

아래 글에서 동적으로 콘트롤 생성/삭제를 보시면 알겠지만, canvasDesigner.selection.selectedElemets 라는
문자열 리스트가 현재 선택된 콘트롤의 이름을 갖고 있습니다. 그 이름을 하나씩 확인하면서, 속성을 지정하면 되겠죠?

아직 구현을 안한 관계로 이 부분은 여러분에게 맡겨 두겠습니다. 알아서들~~~~ㅎㅎ
여기까지 내용에서 질문 있으신 분들은 댓글로 해주세요 ^^

<끝>






















태그 : Background Brushes Foreground
작성자 정보
안떠니
Level 28
 [EXP.46/50]

메일:  비공개
글등록 +12 444 덧글등록 +3 246
자기소개
회사에서 WPF C# 으로 프로그램을 만들라고 하네요 ^^;
글 공유하기 |
  tweet facebook
2013-03-14 오전 1:57:26
나도한마디
사용자
지송닷넷            [2013-03-15]
Level 99
 [EXP.만랩]
if( obj is ContentControl ) obj.SetValue( ContentControl.ContentProperty , sender.GetValue( TextBox.TextProperty ) );
else if (obj is TextBox) obj.SetValue(TextBox.TextProperty, sender.GetValue(TextBox.TextProperty)); 요런식이면 좀 보기 어려울까요?
사용자
안떠니            [2013-03-23]
Level 28
 [EXP.46/50]
퇴근님의 코드가 원래는 정석이죠.
어떤 콘트롤이 툴 윈도우에 추가가 되더라도, 별도의 추가 코드 없이 되니까요

평점좀주세요 ^^;
태그로 엮인글
[WPF Q&A] 특정 cell 배경색 변경[1]  아힝헝홍
[WPF Q&A] RadioButton Style을 적용했는데 IsEnabled = False 일때 전경색이 바뀌지 않네요...[1]+1  비가와요
[WPF Q&A] Window 의 Background 설정[1]  깡베베
[C#.NET Q&A] 선배님들께 DrawString 에 대해 질문 드립니다![1]  푸른소나무
[WPF Q&A] TextBox - SelectionText의 Foreground를 변경하고 싶습니다.[3]+4  개발자
[C#.NET Q&A] 이런 경우 invoke나 background worker가 필요할까요??[1]+2  키라
[WPF Q&A] listviewitem 질문이 있습니다.[1]  귀엽싸리
[WPF Q&A] xaml 에서 리소스 사용 하는 법좀 알려주세요..ㅜ[2]  귀엽싸리
[WPF Q&A] wpf ListView Background 문제[2]  선인장
[C#.NET Q&A] td background를 이용한 탭 기능 ..잘 안됨 ...  노래짱
글리스트
CheckBox ListBox 샘플[1] 파일첨부 이재웅
RadioButton ListBox 샘플 파일첨부 이재웅
UniformGrid ListBox 샘플 파일첨부 이재웅
Horizontal ListBox 샘플 파일첨부 이재웅
Vertical ListBox 샘플[2] 파일첨부 이재웅
 ★현재글->   Form Designer 프로젝트 #7 - Tool Window (콘트롤 속성 지정/변경) - 완료![1]+1  안떠니
Form Designer 프로젝트 #6 - Tool Window (콘트롤 추가/삭제) - 완료![3]+1  안떠니
Form Designer 프로젝트 #5 - TreeView Manager  안떠니
Form Designer 프로젝트 #4 - TreeView + XML [1]  안떠니
Form Designer 프로젝트 #3 - TreeView 삭제 / 이름변경[2]+2  안떠니
Form Designer 프로젝트 #2 - TreeView 노드 추가[1]  안떠니
Form Designer 프로젝트 #1 - 프로젝트 관리 화면[4]+1  안떠니
실행파일에서 config 파일 변경하기[1]+3  킴언어
계산기[3]  sa2랑
WPF 성능관리  sa2랑
[RE] WPF Performance Suite  sa2랑