728x90

 

WPF를 사용하면 전체적인 틀을 잡기 위해 제일 많이 쓰는 태그가 Grid, StackPanel 아닐까요..?

CSS로 따지면 Container.. div.. 같은 느낌..

 

평소 느낌으로,, 아아,, Grid겠지,, 아아,, StackPanel일거야,,하며 사용하고는 했는데, 이번에 ScrollViewer를 사용하면서깨달은 점을 기록할 예정입니다.

 

각각의 특징을 간단히 정리해보고, 특징에 따른 주의사항에 대해 간단히 정리해봅니다!

 

1. Grid

* 행(Row)과 열(Column)로 UI를 구성(Table과 유사)

* RowDefinition과 ColumnDefinition을 활용하여 정확한 크기 조정 가능

* Grid.RowSpan, Grid.ColumnSpan을 사용해 특정 요소를 여러 행 또는 열에 걸쳐 배치 가능

* 정렬이 필요한 UI

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <TextBlock Grid.Row="0" Text="Title" FontSize="20"/>
</Grid>

 

 

2. StackPanel

* 수직 또는 수평으로 자식 요소들을 한 줄로 배치(Flex와 유사)

* 내부 요소의 크기를 모두 합산하여 자신의 크기를 결정

* 레이아웃이 단순해서 버튼 목록, 메뉴 등 간단한 UI에 적합

<StackPanel Orientation="Vertical">
    <Button Content="Button 1"/>
    <Button Content="Button 2"/>
    <Button Content="Button 3"/>
</StackPanel>

 

 

💡3. StackPanel이 Grid보다 사용하기 좋은 경우

No. 상황 이유
1 단순한 리스트형 UI 한 방향으로 요소를 추가하는 UI에서 Grid보다 더 간결하고 코드가 짧음
2 자식 요소 개수가 동적으로 변할 때 내부 요소가 많아져도 자동으로 크기를 조정하므로 레이아웃 관리가 쉬움
3 자동 크기 확장이 필요한 경우 요소 개수에 따라 부드럽게 늘어나야 하는 레이아웃에 적합

 

 

🚨 4. StackPanel 사용 시, 유의 사항

StackPanel은 자식 요소들의 크기를 모두 합쳐서 자신의 크기를 결정하는 방식이므로,  StackPanel 내부에 ScrollViewer가 있을 경우 자식의 전체 크기를 그대로 반영하려하기 때문에, 스크롤 기능을 무시하게 됨

→ ScrollViewer 기능이 필요할 경우, StackPanel이 아닌 Grid를 사용해야 함

 

 

 

📑

참고 자료

Chat GPT

728x90
728x90

 

예? 저에게 데이터가 전달되었다고요?

를 표현한 잔망루피입니다.

 

예전에는 WPF 개발을 MVVM 구조로 진행해서 ViewModel과 Model을 사용해서 데이터를 전달하곤 했는데, 이번에는 개발 디자인 패턴이 바뀌어서 MVC로 진행하게 되었습니다.

 

데이터 전달을 EventHandler라는 것을 사용하게 되는데 간단히 정리해보도록 하겠습니다.

 

1. EventHander 선언

public event EventHandler<string> TitleSelected;
// string 타입의 데이터를 이벤트 핸들러에 전달할 수 있음

 

 

2. EventHanlder가 사용될 이벤트 등록 및 구현

lv_Presents.SelectionChanged += Lv_Presents_SelectionChanged;
// ListView에서 항목이 변경될 때 실행되는 이벤트(Lv_Presents_SelectionChanged)를 등록
// 선택 항목이 바뀔 때마다 메서드가 호출

private void lv_Presents_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (lv_Presents.SelectedItem is PresentModel model)
    {
        TitleSelected?.Invoke(this, model.title);
        // this: 현재 UserControl을 이벤트 발신자로 지정
        // model.title: 전달할 데이터
        // ?.Invoke: 이벤트 구독자에게 데이터 전달(구독자가 없을 때 예외가 발생하지 않음)
    }
}

 

 

3. 이벤트 구독 및 처리로직 구현

uc_Presents.TitleSelected += uc_Presents_TitleSelected;
// uc_Presents TitleSelected 이벤트가 발생하면, uc_Presents_TitleSelected 메서드 실행
// Window가 생성될 때 이벤트 핸들러를 등록하여 UserControl에서 발생한 이벤트를 받을 준비를 함

private void uc_Presents_TitleSelected(object sender, string title)
{
    tblock_SelectedTitle.Text = title;
}

 

 

💡 이벤트 해제

* 객체가 더 이상 사용되지 않거나 이벤트를 더 이상 받을 필요가 없을 때는 -=로 해제하는 것이 좋음

  * 이벤트 핸들러가 해제되지 않으면 구독자가 GC에 의해 수거되지 않기 때문에, 메모리 누수가 발생할 수 있음

  * 여러 번 구독하는 것을 방지

 

 

 

📑

참고 자료

Chat GPT

 

728x90
728x90

xaml에서 ⭐나만의 property⭐를 만들 수 있다는 걸 알고 계셨나요?

전 알고 있었습니다. 하지만, ⭐나만의 property⭐가 없어도 구현에 문제가 없어서 Dependency Property를 공부하는 걸 미루다가 프로젝트에서 사용되는 걸 보고, 이 참에 정리하고자 글을 작성해 봅니다.

 

 

 

Dependency Property

  * XAML, C# 코드 비하인드(.xaml.cs)에서 사용 가능

  * 의존속성 값이 변경되면 자동으로 어떤 것을 로드되게 하거나 랜더링 되도록 할 수 있음

    * 애니메이션, 스타일링, 데이터바인딩 등에 자주 사용

  * 기본으로 제공되는 UI 컨트롤은 대부분의 속성이 의존 속성으로 되어 있음

 

 

1. MainWindow에서 바로 사용하기

MainWindow.xaml

<Window x:Class="DependencyPropertyPratice.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:DependencyPropertyPratice"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        >
    <Window.ContextMenu>
        <ContextMenu MenuItem.Click="ContextMenu_Click">
            <MenuItem Header="#02343F"/>
            <MenuItem Header="#F0EDCC"/>
            <MenuItem Header="#0A174E"/>
            <MenuItem Header="#F5D042"/>
        </ContextMenu>
    </Window.ContextMenu>

    <TextBox Name="colorBox"
             VerticalAlignment="Center" HorizontalAlignment="Center"
             Width="150" Height="50"
             />
</Window>

 

MainWindow.xaml.cs

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

namespace DependencyPropertyPratice
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        // DependencyProperty(MyColorProperty)를 위한 래퍼 속성의 MyColor
        // 이 래퍼속성에서는 System.Windows.DependencyObject 클래스의 GetValue()와 SetValue()를 이용해서 get, set을 정의해야 함
        public string MyColor
        {
            get { return (string)GetValue(MyColorProperty); }
            set { SetValue(MyColorProperty, value); }
        }

        // DependencyProperty
        // DependencyProperty 클래스에는 public 생성자가 없으므로 static 메서드인 Register()를 사용해서 등록
        // 수정이 불가능 하도록 의존 속성은 readonly 필드로 선언되는데, 일반 UI 컨트롤 등 대부분의 의존속성은 FrameworkElement에 DependencyProperty로 정의되어 있음
        public static readonly DependencyProperty MyColorProperty = DependencyProperty.Register(
            "MyColor" // 의존 속성으로 등록될 속성
            , typeof(string) // 등록할 의존 속성 타입
            , typeof(MainWindow) // 의존 속성을 소유하게 될 Owner
            , new FrameworkPropertyMetadata(new PropertyChangedCallback(OnMyColorPropertyChanged))
            // 속성값 변경 시, 호출될 메서드
            // Property 값 변경에 따른 Callback() 등 새로운 속성을 추가하기 위해 FrameworkPropertyMetadata를 인자 값으로 전달 할 수 있음
            );

        private static void OnMyColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MainWindow window = d as MainWindow;
            SolidColorBrush brush = (SolidColorBrush)new BrushConverter().ConvertFromString(e.NewValue.ToString());
            window.Background = brush;
            window.Title = (e.OldValue == null) ? "이전 배경색 없음" : e.OldValue.ToString();
            window.colorBox.Text = e.NewValue.ToString();
        }

        private void ContextMenu_Click(object sender, RoutedEventArgs e)
        {
            string selectedColor = (e.Source as MenuItem).Header as string;
            MyColor = selectedColor;
        }

    }
}

 

 

2. SubWindow에서 상속받아서 사용하기

SubWindow.xaml

<local:MainWindow x:Class="DependencyPropertyPratice.WindowSub"
        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:DependencyPropertyPratice"
        mc:Ignorable="d"
        Title="WindowSub" Height="450" Width="800"
        MyColor="#F95700"
        >
    <Grid>
        <Button Content="Go To the Main Window"
                Width="150" Height="30"
                Click="Button_Click"
                />
    </Grid>
</local:MainWindow>

 

☠️ 이렇게 못씁니다.

오류납니다.

'DependencyPropertyPratice.MainWindow' cannot be the root of a XAML file because it was defined using XAML.

 

WPF에서는 XAML 파일을 통해 이미 정의된 클래스를 다시 XAML의 루트 요소로 사용하는 것을 허용하지 않습니다.

 

상속을 받고자 한다면, MainWindow를 XAML이 아닌 코드 비하인드 파일(C# 파일)에서만 정의하여 SubWindow에서 이를 상속할 수 있도록 할 수 있습니다.

 

 

MainWindowOnlyCS.cs

using System.Windows;
using System.Windows.Media;

namespace DependencyPropertyPratice
{
    public class MainWindowOnlyCS : Window
    {
        public string MyColor
        {
            get { return (string)GetValue(MyColorProperty); }
            set { SetValue(MyColorProperty, value); }
        }

        public static readonly DependencyProperty MyColorProperty = DependencyProperty.Register(
            "MyColor" // 의존 속성으로 등록될 속성
            , typeof(string) // 등록할 의존 속성 타입
            , typeof(MainWindowOnlyCS) // 의존 속성을 소유하게 될 Owner
            , new FrameworkPropertyMetadata(new PropertyChangedCallback(OnMyColorPropertyChanged))
            // 속성값 변경 시, 호출될 메서드
            );

        private static void OnMyColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MainWindowOnlyCS window = d as MainWindowOnlyCS;
            SolidColorBrush brush = (SolidColorBrush)new BrushConverter().ConvertFromString(e.NewValue.ToString());
            window.Background = brush;
            window.Title = (e.OldValue == null) ? "이전 배경색 없음" : e.OldValue.ToString();
        }
    }
}

 

WindowSub.xaml

<local:MainWindowOnlyCS x:Class="DependencyPropertyPratice.WindowSub"
        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:DependencyPropertyPratice"
        mc:Ignorable="d"
        Title="WindowSub" Height="450" Width="800"
        MyColor="#F95700"
        >
    <Grid>
        <Button Content="Go To the Main Window"
                Width="150" Height="30"
                Click="Button_Click"
                />
    </Grid>
</local:MainWindowOnlyCS>

 

.cs로 이뤄진 Window 파일을 상속받아서 xaml 태그에 MyColor를 전달하면 DependencyProperty에 따라 WindowSub의 배경색이 오랜지 색으로 변경됩니다.

 

 

 

 

📚 참고 자료

 

Reference Source

 

referencesource.microsoft.com

 

 

728x90
728x90

xaml에서 비슷한 두 개의 코드에 style을 적용시켰는데, 하나의 코드에서는 동작하고 하나의 코드에서는 동작을 하지 않았습니다. 이유를 간단하게 정리해 보고자 합니다.


저에겐 MouseOver 시, 글씨를 굵게 만들어주는 Style이 하나 있습니다.

 

<Style x:Key="DefaultButton" TargetType="Button">
    <Setter Property="Background" Value="DodgerBlue"/>
    <Setter Property="Foreground" Value="#FFFFFF"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border x:Name="Border" 
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        CornerRadius="5">
                    <ContentPresenter HorizontalAlignment="Center" 
                                      VerticalAlignment="Center"/>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="FontWeight" Value="Bold" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

 

스타일을 한 번 정의해두면, 여러 UI 요소에 동일한 스타일을 적용할 수 있어 코드의 재사용성이 높아집니다.

 

DodgerBlue 색상의 호버 효과가 있는 버튼은 이제 저에게 식은죽 먹기가 됩니다.

 

<Button Style="{StaticResource DefaultButton}">
    <TextBlock Text="Hello"/>
</Button>
<Button Style="{StaticResource DefaultButton}"
        Content="Hello"
        >

 

 

하지만, 아래 코드에서는 Button안의 TextBlock에 FontWeight를 직접적으로 명시했다가는 DodgerBlue 색상만 가진 버튼이 됩니다.

 

<Button Style="{StaticResource DefaultButton}">
    <TextBlock Text="Hello"
               FontWeight="Light"
               />
</Button>
<Button Style="{StaticResource DefaultButton}"
        Content="Hello"
        FontWeight="Light"
        >

 

그 차이를 간단히 정리해 보고자 합니다.

 

먼저, Sytle에 설정된 내용을 간력히 정리해보면, 다음과 같습니다.

 

 

1. ControlTemplate

Control Template , 즉 <> 태그의 외형을 정의합니다.

ControlTemplate을 사용할 경우, 기본 Template을 덮어씁니다.

 

2. ContentPresenter

새로운 Template을 만들어 사용할 경우, 내용(Content)을 표시하는 기능인 ContentPresenter도 함께 선언해야 합니다.

 

3. TemplateBinding

ControlTemplate 안에서 외부의 속성 값을 바인딩할 때 사용합니다.

여기서는 DefaultButton을 사용한 Button의 Background인 DodgerBlue를 참조하게 됩니다.

 

4. Trigger

마지막으로 trigger는 특정 조건이 만족될 때(여기서는 마우스가 버튼 위에 올라왔을 때) 스타일이나 속성을 변경합니다. 이를 통해 ControlTemplate에서 설정된 속성들을 변경할 수 있습니다.

 

 

1번 코드와 2번 코드는 Button 안의 TextBlock에 FontWeight를 직접 설정했느냐의 차이가 있습니다.

 

1번은

  Complate.Trigger → Button 속성 변경 → ContentPresenter: 변경된 속성을 Button의 자식 요소인 TextBlock에 전달합니다.

2번은

  Complate.Trigger → Button 속성 변경 → ContentPresenter: 변경된 속성을 Button의 자식 요소인 TextBlock에 전달합니다. 하지만, 자식 요소에서 직접 설정된 속성은 ControlTemplate의 외부에서 설정된 것으로 간주되어, ControlTemplate의 트리거에서 영향을 받지 않습니다. 

 

간단하게 정리하면, ControlTemplate은 전체적인 Template를 control할 수 있지만, <> 안에서 직접 설정된 요소는 변경할 수 없습니다.

 

 

 

📚 참고 자료

 

[c# wpf] Control Template 란? / 사용방법 (with Style)

이 글을 읽기 전 선수 지식 포스팅 [c# wpf] Style 태그 사용 방법 [c# wpf] Style 상속 방법(with BasedOn) [c# wpf] StaticResource란? (DynamicResource 비교) 왼쪽 그림은 우리가 아는 기본적인 버튼에 빨간 배경색을

yeko90.tistory.com

 

728x90
728x90

현재 프로젝트를 진행하고 있는데, 특정 API를 호출해서 사용하고 있습니다.

 

무얼 정리하면 좋을까하다가 java와 달리 C#에서는 필드라는 개념이 추가적으로 있는데, 이와 관련하여 정리해보고자 합니다.

 


생성자를 이용한 초기화

1
2
3
4
5
6
7
8
9
10
11
private string _initializationConstructor;
public string InitializationConstructor
{
    get { return _initializationConstructor; }
    set { _initializationConstructor = value; }
}
 
public ViewModelMain()
{
    InitializationConstructor = "Initialize with Constructor";
}

특징

 * 생성자 매개변수를 통해 초기값을 유연하게 설정

 * 클래스의 필드 및 속성 초기화가 모두 끝난 후에 생성자가 호출

 

 

필드에서의 직접 초기화

1
2
3
4
5
6
7
private string _initializationField = "Initialize with Field";
 
public string InitializationField
{
    get { return _initializationField; }
    set { _initializationField = value; }
}

특징

 * 간단한 값을 초기화할 때 사용

 * 생성자 호출 전에 초기화

 

만일 필드에서의 초기값과 생성자를 활용한 초기값을 한 번에 설정한다면?

생성자는 필드 및 속성 초기화가 끝난 후에 호출되기 때문에, 필드 초기화한 값을 생성자에서 다시 설정할 경우 생성자 기준으로 값이 설정됨

 

728x90

'C# > WPF' 카테고리의 다른 글

[WPF] Dependency Property  (0) 2024.07.07
[WPF] ControlTemplate의 영향력  (0) 2024.06.30
[WPF] MVVM 패턴에서의 전체 선택  (0) 2024.05.19
[WPF] ObservableCollection  (0) 2024.05.15
[WPF] View, ViewModel Singleton  (0) 2024.05.12