.NET을 사용하여 목록 상자에 이름을 추가하는 WPF 앱을 제작할 것이다.
Visual Studio를 사용해 창에 컨트롤을 추가하여 앱의 UI를 디자인하고, 해당 컨트롤의 입력 이벤트를 처리하여 사용자와 상호 작용하게끔 만든다.
Visual Studio에서 WPF에 대한 지원에는 앱을 만들 때 상호 작용하는 5가지 중요한 구성 요소가 있다.


1.솔루션 탐색기
모든 프로젝트 파일, 코드, 창, 리소스가 이 창에 표시된다.
2.속성
이 창에는 선택한 항목에 따라 구성할 수 있는 속성 설정이 표시된다. 예를 들어 솔루션 탐색기에서 항목을 선택하면 해당 파일과 관련된 속성 설정이 표시된다. 디자이너에서 개체를 선택하면 요소에 대한 설정이 표시된다.
3.도구 상자
도구 상자에는 디자인 화면에 추가할 수 있는 모든 컨트롤이 포함되어 있다. 현재 화면에 컨트롤을 추가하려면 컨트롤을 두 번 클릭하거나 컨트롤을 화면에 끌어서 놓는다. XAML 디자이너 창을 사용하여 결과를 미리 보는 동안 XAML 코드 편집기 창을 사용하여 UI(사용자 인터페이스)를 디자인하는 것이 일반적이다.
4.XAML 디자이너
XAML 문서용 디자이너이다. 대화형으로 작동하며, 도구 상자에서 개체를 끌어다 놓을 수 있다. 디자이너에서 항목을 선택하고 이동하여 앱에 대한 UI를 시각적으로 작성할 수 있다.
디자이너와 편집기가 모두 표시되는 경우 하나에 대한 변경 내용이 다른 것에도 반영된다.
디자이너에서 항목을 선택하면 속성 창에 해당 개체에 대한 속성과 특성이 표시된다.
5.XAML 코드 편집기
XAML 문서용 XAML 코드 편집기다. XAML 코드 편집기는 디자이너 없이 UI를 수동으로 작성하는 방법이다. 디자이너는 컨트롤이 디자이너에 추가될 때 컨트롤의 속성을 자동으로 설정할 수 있다. XAML 코드 편집기는 더 많은 제어 기능을 제공한다.
디자이너와 편집기가 모두 표시되는 경우, 하나에 대한 변경 내용이 다른 것에도 반영된다. 코드 편집 기에서 텍스트 캐리트를 탐색할 때 속성 창에는 해당 개체에 대한 속성과 특성이 표시된다.
<Window x:Class="Names.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Names"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
</Grid>
</Window>
XAML 코드
이 XAML 코드는 WPF (Windows Presentation Foundation) 윈도우의 기본 구조를 정의한다.
Window: WPF 창을 나타내는 '루트' 요소이다. x:Class="Names.MainWindow": 이 XAML 코드와 연결될 C# 클래스를 지정한다. Names 네임스페이스의 MainWindow 클래스가 이 창의 코드 비하인드 클래스가 된다. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation": WPF 네임스페이스를 지정한다. WPF UI 요소들을 사용하려면 이 네임스페이스를 선언해야 한다.xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml": XAML 네임스페이스를 지정한다. XAML 언어의 기본적인 기능을 사용하려면 이 네임스페이스를 선언해야 한다.xmlns:d="http://schemas.microsoft.com/expression/blend/2008": Blend 도구에서 사용하는 네임스페이스를 지정한다. Blend는 WPF UI 디자인 도구이다.xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006": 마크업 호환성 네임스페이스를 지정한다. XAML 코드의 호환성을 유지하기 위해 사용된다.xmlns:local="clr-namespace:Names": Names 네임스페이스를 local 접두사로 매핑한다. 이를 통해 Names 네임스페이스의 클래스를 XAML에서 사용할 수 있다.mc:Ignorable="d": Blend 네임스페이스의 요소를 무시하도록 지정한다. Blend에서 사용하는 디자인 타임 속성은 런타임에는 필요하지 않으므로 무시하도록 설정한다.Title="MainWindow": 창의 제목을 "MainWindow"로 설정한다.Height="450": 창의 높이를 450으로 설정한다.Width="800": 창의 너비를 800으로 설정한다.<Grid>: 창의 내용을 담는 컨테이너 컨트롤이다. Grid 컨트롤은 행과 열을 사용하여 UI 요소를 배치할 수 있는 컨테이너이다.이 코드는 WPF 창의 기본적인 구조를 정의하고, 창의 제목, 크기, 네임스페이스 등을 설정한다.
루트 <Window>
여기서 루트 <Window> 는 XAML 파일에서 설명하는 객체의 형식을 나타낸다. 위 코드에서는 선언된 애트리뷰트(특성)가 8 개이며, 이들은 일반적으로 3 개의 범주(XML namespaces, x:Classattribute, Titleattribute)에 속한다.
XML 네임스페이스
: XML 네임스페이스는 XML에 대한 구조체를 제공하여 파일에서 선언할 수 있는 XML 콘텐츠를 결정합니다.
기본 xmlns 특성은 전체 파일에 대한 XML 네임스페이스를 가져오며 이 경우에는 WPF에 의해 선언된 형식에 매핑됩니다. 다른 XML 네임스페이스는 접두사를 선언하고 XAML 파일에 대한 다른 형식 및 개체를 가져옵니다. 예를 들어 xmlns:local 네임스페이스는 local 접두사를 선언하고 프로젝트에 의해 선언된 개체, Names 코드 네임스페이스에서 선언된 개체에 매핑됩니다.
x:Class 애트리뷰트
: 이 애트리뷰터(특성)는 코드 <Window> 또는 MainWindow.xaml.vb 파일(C# 및 Visual Basic의 Names.MainWindow 클래스)에 매핑 MainWindow 됩니다.
Title 애트리뷰트
: XAML 개체에 선언된 모든 일반 특성은 해당 개체의 속성을 설정합니다. 이 경우 Title 특성은 Window.Title 속성을 설정합니다.
기존 창

XAML 코드 편집기의 코드
<Window Title="MainWindow" Height="450" Width="800">에서,
Title을 Names로 변경하여 창의 제목을 변경한다.
Width를 180, Height를 260 으로 변경하여 창의 크기를 변경한다.
변경한 창

<Window Title="Names" Height="180" Width="260">
WPF는 다양한 '레이아웃 컨트롤'을 갖춘 강력한 '레이아웃 시스템'을 제공한다. 레이아웃 컨트롤은 자식 컨트롤을 배치하고 크기를 조정하는 데 도움이 되며 이를 자동으로 수행할 수도 있다.
WPF는 다양한 레이아웃 컨트롤을 제공하는데, 이를 통해 개발자는 요소들의 크기, 위치, 정렬 방식 등을 유연하게 조절할 수 있다.
주요 레이아웃 컨트롤과 특징은 다음과 같다.
Grid:<Grid> 컨트롤을 배치할 수 있으며, 새 그리드는 더 많은 행과 열을 정의하고 고유한 자식을 가질 수 있다.<Grid> 컨트롤은 자식 컨트롤을 행과 열에 배치한다. 그리드에는 항상 단일 행과 열이 선언되어 있다. 즉, 기본적으로 그리드는 단일 셀이다. 이 상태에서는 컨트롤을 유연하게 배치할 수 없다.)StackPanel:
Orientation 속성을 사용하여 배치 방향을 설정할 수 있다.DockPanel:
DockPanel.Dock 속성을 사용하여 요소를 도킹할 방향(상, 하, 좌, 우)을 설정할 수 있다.WrapPanel:
Orientation 속성을 사용하여 배치 방향을 설정할 수 있다.Canvas:
Canvas.Left, Canvas.Top 속성을 사용하여 요소의 위치를 지정할 수 있다.레이아웃 컨트롤 선택 기준
WPF 레이아웃은 다양한 컨트롤을 제공하여 UI를 유연하게 구성할 수 있도록 지원한다.
<Grid>
</Grid>

2.<Grid> 요소에 새로운 속성인 Margin="10"을 추가한다.
이 설정을 사용하면 창 가장자리에서 격자가 안쪽으로 들어와 조금 더 보기 좋게 보인다.
<Grid Margin="10">
</Grid>

<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
</Grid>

<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>

이 XAML 코드는 Grid 컨트롤을 사용하여 UI 요소를 배치하기 위한 기본적인 틀을 만드는 코드이다.
Grid 컨트롤은 행과 열을 사용하여 UI 요소를 격자 형태로 배치할 수 있는 컨테이너 컨트롤이다. 마치 엑셀 스프레드시트처럼 행과 열로 이루어진 격자를 생각하면 된다.
<Grid Margin="10">는 Grid 컨트롤을 생성하고, 컨트롤의 바깥쪽 여백을 10으로 설정한다.
<Grid.RowDefinitions>는 Grid 컨트롤의 행을 정의하는 부분이다.
<RowDefinition Height="*"/>는 높이가 *인 행을 정의한다. *는 사용 가능한 공간을 균등하게 나누어 사용한다는 의미이다. 즉, 이 코드는 높이가 같은 두 개의 행을 생성한다.<Grid.ColumnDefinitions>는 Grid 컨트롤의 열을 정의하는 부분이다.
<ColumnDefinition Width="*"/>는 너비가 *인 열을 정의한다. 마찬가지로 *는 사용 가능한 공간을 균등하게 나누어 사용한다는 의미이다. 즉, 이 코드는 너비가 같은 두 개의 열을 생성한다.이 코드를 통해 2x2 격자 형태의 Grid 컨트롤이 생성된다. 이제 이 격자 안에 다른 UI 요소들을 배치하여 UI를 디자인할 수 있다.
그리드가 만들어졌으므로 컨트롤을 추가할 수 있다. 먼저 Label 컨트롤부터 시작한다.
<Grid> 요소 안에, 행과 열 정의 다음에 새로운 <Label> 요소를 만든다. 이 요소의 내용을 문자열 값 "Names"로 설정한다.<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label>Names</Label>
</Grid>
<Label>Names</Label>는 Names라는 내용을 정의한다. 어떤 컨트롤은 내용을 처리하는 방법을 알고 있지만, 그렇지 않은 컨트롤도 있다. 컨트롤의 내용은 Content 프로퍼티에 매핑된다. XAML 속성 구문을 통해 내용을 설정하려면 <Label Content="Names" /> 형식을 사용한다. 두 가지 방법 모두 레이블의 내용을 텍스트 Names로 설정하는 동일한 결과를 가져온다.

레이블이 창의 절반을 차지하는 것을 볼 수 있는데, 이는 그리드의 첫 번째 행과 열에 자동으로 배치되었기 때문이다. 첫 번째 행에는 레이블만 넣을 것이므로 그렇게 많은 공간이 필요하지 않다.
<RowDefinition>의 Height 속성을 *에서 Auto로 변경한다.<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
Auto 값은 그리드 행의 크기를 내용물의 크기에 맞게 자동으로 조정한다. 이 경우에는 레이블 컨트롤의 크기에 맞춰진다.

이제 디자이너에서 레이블이 사용 가능한 높이의 작은 부분만 차지하는 것을 볼 수 있다. 다음 행이 더 많은 공간을 차지할 수 있게 되었다.
컨트롤 배치에 대해 알아보겠다. 이전 섹션(컨트롤 추가)에서 만든 레이블(Label)은 자동으로 그리드의 0행 0열에 배치되었다. 행과 열의 번호는 0부터 시작해서 1씩 증가한다. 컨트롤은 그리드에 대해 아무것도 모르고, 그리드 내에서의 위치를 제어하는 프로퍼티도 정의하지 않는다.
컨트롤이 그리드에 대해 아무것도 모르는데 어떻게 다른 행이나 열을 사용하도록 지정할 수 있을까? 연결된 프로퍼티(Attached Properties)를 사용하면 된다.
Grid 컨트롤은 WPF에서 제공하는 강력한 프로퍼티 시스템을 활용한다. 그리드 컨트롤은 자식 컨트롤이 자신에게 연결할 수 있는 새로운 프로퍼티를 정의한다. 이러한 프로퍼티는 실제로 컨트롤 자체에 존재하는 것이 아니라, 컨트롤이 그리드에 추가될 때 컨트롤에서 사용할 수 있게 된다.
그리드는 자식 컨트롤의 행과 열 위치를 결정하기 위해 두 가지 프로퍼티를 정의한다. 바로 Grid.Row와 Grid.Column이다. 이러한 프로퍼티가 컨트롤에서 생략되면 기본값 0을 가지는 것으로 간주되어 컨트롤은 그리드의 0행 0열에 배치된다.
<Label> 컨트롤의 위치를 변경하려면, Grid.Column 속성을 1로 설정하면 된다.
<Label Grid.Column="1">Names</Label>
이렇게 하면 레이블이 두 번째 열로 이동한다. Grid.Row와 Grid.Column 연결된 프로퍼티를 사용하여 다음에 만들 컨트롤의 위치를 지정할 수 있다.

이제 XAML 코드에서 이름 목록을 표시할 리스트 박스 컨트롤을 추가한다.
<ListBox /> 컨트롤을 <Label> 컨트롤 아래에 선언한다.
Grid.Row 속성을 1로 설정하여 리스트 박스가 레이블 아래 행에 위치하도록 한다.
x:Name 속성을 lstNames로 설정하여 리스트 박스에 이름을 부여한다. 컨트롤에 이름을 지정하면, 코드 비하인드에서 해당 컨트롤을 참조할 수 있다. x:Name 애트리뷰트를 사용하여 컨트롤에 이름을 할당한다.
이렇게 하면 XAML 코드는 다음과 같다.
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label>Names</Label>
<ListBox Grid.Row="1" x:Name="lstNames" />
</Grid>

이제 텍스트 박스와 버튼을 추가해서, 사용자가 리스트 박스에 이름을 추가할 수 있도록 한다. 이 컨트롤들을 배치하기 위해 그리드에 더 많은 행과 열을 만드는 대신에,<StackPanel>이라는 레이아웃 컨트롤을 사용한다.
StackPanel 컨트롤은 Grid 컨트롤과 컨트롤 배치 방식이 다르다. Grid 컨트롤에서는 Grid.Row와 Grid.Column 속성을 사용하여 컨트롤의 위치를 명시적으로 지정해야 하지만, StackPanel 컨트롤은 자식 컨트롤을 순차적으로 자동으로 배치한다. 즉, 컨트롤을 차례대로 쌓아 올리는 방식이다.
<ListBox> 컨트롤 아래에 <StackPanel> 컨트롤을 선언한다.Grid.Row 속성을 1로 설정한다.Grid.Column 속성을 1로 설정한다.Margin을 5,0,0,0으로 설정한다. Margin 특성(애트리뷰트)은 이전에 Grid에서 사용되었지만, 그때는 10이라는 단일 값만 사용했다. 이번에는 5,0,0,0이라는 값을 사용하는데, 이는 10과는 매우 다르다. Margin 특성은 Thickness 타입으로, 두 가지 값을 모두 해석할 수 있다. Thickness는 사각형 프레임의 각 면(왼쪽, 위쪽, 오른쪽, 아래쪽) 주변의 공간을 정의한다. Margin에 단일 값을 지정하면 네 면 모두에 해당 값이 사용된다.<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label>Names</Label>
<ListBox Grid.Row="1" x:Name="lstNames" />
<StackPanel Grid.Row="1" Grid.Column="1" Margin="5,0,0,0">
</StackPanel>

<StackPanel> 컨트롤 안에 <TextBox /> 컨트롤을 생성한다. x:Name 속성을 txtName으로 설정한다.
마지막으로 <TextBox> 다음에 <StackPanel> 안에 <Button> 컨트롤을 생성한다.
a. x:Name 속성을 btnAdd로 설정한다.
b. Margin을 0,5,0,0으로 설정한다.
c. 내용을 "Add Name"으로 설정한다.
XAML 코드는 다음과 같다.
<StackPanel Grid.Row="1" Grid.Column="1" Margin="5,0,0,0">
<TextBox x:Name="txtName" />
<Button x:Name="btnAdd" Margin="0,5,0,0">Add Name</Button>
</StackPanel>

이렇게 하면 윈도우의 레이아웃이 완성된다. 하지만 아직 애플리케이션에는 실제 기능을 수행하는 로직이 없다. 다음 단계에서는 컨트롤 이벤트를 코드에 연결하고 애플리케이션이 실제로 동작하도록 만드는 것이다.
<Button> 컨트롤에는 사용자가 버튼을 누를 때 발생하는 Click 이벤트가 있다. 이 이벤트를 설정하고 코드를 추가하여 리스트 박스에 이름을 추가할 수 있다. XAML 애트리뷰트(속성)는 프로퍼티를 설정하는 것처럼 이벤트를 설정하는 데에도 사용된다.
<Button> 컨트롤을 찾는다.Click 애트리뷰트를 ButtonAddName_Click으로 설정한다.<StackPanel Grid.Row="1" Grid.Column="1" Margin="5,0,0,0">
<TextBox x:Name="txtName" />
<Button x:Name="btnAdd" Margin="0,5,0,0" Click="ButtonAddName_Click">Add Name</Button>
</StackPanel>
ButtonAddName_Click에 마우스 우클릭하고 "정의로 이동"을 선택한다. 이렇게 하면 지정된 핸들러 이름과 일치하는 메서드(껍데기)가 코드 비하인드에 생성된다.private void ButtonAddName_Click(object sender, RoutedEventArgs e)
{
}
private void ButtonAddName_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(txtName.Text) && !lstNames.Items.Contains(txtName.Text))
{
lstNames.Items.Add(txtName.Text);
txtName.Clear();
}
}
이 C# 코드는 ButtonAddName_Click 이벤트 핸들러로, 버튼이 클릭되었을 때 텍스트 박스의 내용을 리스트 박스에 추가하는 기능을 수행한다.
if (!string.IsNullOrWhiteSpace(txtName.Text) && !lstNames.Items.Contains(txtName.Text)) 조건문은 텍스트 박스에 입력된 값이 유효한지 확인한다.
!string.IsNullOrWhiteSpace(txtName.Text): 텍스트 박스가 비어 있거나 공백 문자로만 채워져 있지 않은지 확인한다.!lstNames.Items.Contains(txtName.Text): 리스트 박스에 이미 같은 이름이 있는지 확인한다.두 조건을 모두 만족하는 경우, 즉 텍스트 박스에 유효한 이름(정상적인 이름)이 입력되었고, 리스트 박스에 같은 이름이 없는 경우에만 lstNames.Items.Add(txtName.Text);를 실행하여 리스트 박스에 이름을 추가한다.
txtName.Clear();는 텍스트 박스의 내용을 지운다.

'애트리뷰트'와 '속성'이라는 용어를 혼용해서 사용했는데, 엄밀히 말하면 둘은 다른 개념이다.
속성(Property)은 XAML 요소의 특징을 정의하는 값이다. 예를 들어, Button 요소의 Content 속성은 버튼에 표시될 텍스트를 나타내고, Background 속성은 버튼의 배경색을 나타낸다.
애트리뷰트(Attribute)는 XAML 요소에 추가적인 정보를 제공하는 값이다. 예를 들어, Grid.Row 애트리뷰트는 Grid 컨트롤 내에서 요소가 위치할 행을 지정하고, Margin 애트리뷰트는 요소 주변의 여백을 지정한다.
XAML에서 속성과 애트리뷰트는 모두 이름-값 쌍으로 표현된다. 하지만 속성은 XAML 요소의 고유한 특징을 정의하는 반면, 애트리뷰트는 XAML 요소에 추가적인 정보를 제공하는 데 사용된다.
이전 설명에서 Margin은 애트리뷰트가 맞지만, Grid.Row와 Grid.Column, x:Name은 첨부 속성(Attached Property)이다. 첨부 속성은 다른 요소에 정의된 속성을 사용할 수 있도록 하는 기능이다.
예를 들어, Grid.Row 속성은 Grid 요소에 정의되어 있지만, StackPanel 요소에서도 Grid.Row 속성을 사용하여 Grid 컨트롤 내에서 StackPanel 요소의 위치를 지정할 수 있다.
요약하자면, XAML에서 속성은 요소의 특징을 정의하고, 애트리뷰트는 요소에 추가적인 정보를 제공한다. Grid.Row, Grid.Column, x:Name은 첨부 속성으로, 다른 요소에 정의된 속성을 사용할 수 있도록 하는 기능이다.