티스토리 뷰
반응형
2022.10.06 - [WPF .NET] - 사용자 정의 컨트롤을 만들기 Custom XAML Control - part1
이전 part1에서 만들었던 컨트롤을 Custom Control를 이용해서 다시 만들어 보도록 하겠습니다.
1. CustomControl 추가
솔루션 탐색기 -> 프로젝트 선택 -> 마우스 오른쪽 -> Add -> New Item
2. CustomControl의 특징
- Code와 Xaml이 서로 분리되어 있습니다.
- CustomUserConsent.cs
- Themes/Generic.xaml 파일 내부에 Style로 정의된 CustomUserConsent
- Themes/Generic.xaml 파일은 App 전역 리소스에 자동으로 등록되는 특수한 성격의 ResourceDictionary입니다.
- ResourceDictionary 내부에 주석 이외의 한글을 입력하면, 컴파일시 오류가 발생할 수 있습니다.
- 리소스를 최적화해서 메모리 소비, 성능 및 접근성을 향상 시키며, 시각적 일관성을 유지할 수 있습니다.
- ControlTemplate 내부에 UI 형태를 자유롭게 구성할 수 있습니다.
- 약간 제한적인 바인딩만 사용 가능합니다.(Dependency Property만 바인딩 가능)
- TemplateBinding 사용해서 바인딩하는 경우
- OneWay 바인딩을 사용해야 할 때
- RelativeSource의 TemplatedParent를 이용해서 바인딩하는 경우
- TwoWay 바인딩을 사용해야 할 때
- Converter를 이용해야할 때
- WPF TemplateBinding vs RelativeSource TemplatedParent - Stack Overflow 차이점 설명
- 다수의 TemplatedParent를 사용하는 경우 컨트롤 성능이 저하될 수 있기 때문에 최소한으로 사용합니다.
- TemplateBinding 사용해서 바인딩하는 경우
- Xaml로 만들어진 컨트롤 중 일부를 Code에서 사용하기 위해서는 다음과 같이 처리합니다.
- Xaml 컨트롤에 x:Name을 이용해서 이름을 지정합니다. 기본적으로 "PART_..."라는 이름으로 시작합니다.
- CustomUserConsent.cs 상단에 이 컨트롤에서 반드시 필요한 Child 컨트롤들의 정보를 추가합니다.
- private const string _partSubmit = "PART_Submit";
- [TemplatePart(Name = _partSubmit, Type = typeof(Button))]
- OnApplyTemplate() 메소드를 override해서 Child 컨트롤을 가져옵니다.
- _submitButton = GetTemplateChild(_partSubmit) as Button;
- 이렇게 처리하는 이유는 혹시 이 컨트롤을 상속 받아서 다른 컨트롤을 만들때, ControlTemplate 내부에 반드시 포함되어야 하는 컨트롤들의 정보를 나열하는 것입니다.
Binding RelativeSource={RelativeSource Mode=TemplatedParent}}를 이용해서 Parent를 찾는 방법이 ControlTemplate와 ContentTemplate에서 사용이 가능하며, ContentTemplate에서 사용하는 경우 ContentPresenter를 반환합니다.
3. CustomUserConsent.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace CustomControlSample
{
/// <summary>
/// 사용자 동의 - CustomControl
/// </summary>
[TemplatePart(Name = _partSubmit, Type = typeof(Button))]
[TemplatePart(Name = _partExit, Type = typeof(Button))]
public class CustomUserConsent : Control, IDisposable
{
private const string _partSubmit = "PART_Submit";
private const string _partExit = "PART_Exit";
private Button? _submitButton;
private Button? _exitButton;
private bool disposedValue;
static CustomUserConsent()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomUserConsent), new FrameworkPropertyMetadata(typeof(CustomUserConsent)));
}
public bool IsUserConsent
{
get => (bool)GetValue(IsUserConsentProperty);
set => SetValue(IsUserConsentProperty, value);
}
/// <summary>
/// 사용자 동의 여부 - DP
/// </summary>
public static readonly DependencyProperty IsUserConsentProperty =
DependencyProperty.Register(nameof(IsUserConsent), typeof(bool), typeof(CustomUserConsent), new PropertyMetadata(false));
/// <summary>
/// 팝업 닫기 커맨드 - 생성시 할당됨
/// </summary>
public ICommand? ClosePopupCommand { get; set; }
public override void OnApplyTemplate()
{
_submitButton = GetTemplateChild(_partSubmit) as Button;
_exitButton = GetTemplateChild(_partExit) as Button;
if (_submitButton == null || _exitButton == null)
{
throw new NullReferenceException($"{_partSubmit} and {_partExit} button cannot be null");
}
_submitButton.Click += SubmitButton_Click;
_exitButton.Click += ExitButton_Click;
}
private void ExitButton_Click(object sender, RoutedEventArgs e)
{
if (ClosePopupCommand != null)
{
ClosePopupCommand.Execute(false);
}
}
private void SubmitButton_Click(object sender, RoutedEventArgs e)
{
if (ClosePopupCommand != null)
{
ClosePopupCommand.Execute(IsUserConsent);
}
}
/// <summary>
/// Dispose pattern
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
if (_submitButton != null)
{
_submitButton.Click -= SubmitButton_Click;
_submitButton = null;
}
if (_exitButton != null)
{
_exitButton.Click -= ExitButton_Click;
_exitButton = null;
}
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
disposedValue = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~CustomUserConsent()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
_submitButton, _exitButton 등의 컨트롤을 Code에서 사용하는 이유는, 클릭 이벤트를 핸들링해야 하기 때문입니다.
4. CustomUserConsent Style
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControlSample">
<Style TargetType="local:CustomUserConsent">
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Background" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomUserConsent">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel Margin="4" HorizontalAlignment="Center">
<TextBlock TextWrapping="Wrap">
Consent to use the application<LineBreak /><LineBreak />
In order to use this application, you must agree to the items below.<LineBreak />
1. Definition...</TextBlock>
<CheckBox
x:Name="PART_UserConsent"
Margin="0,10,0,0"
IsChecked="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=IsUserConsent, Mode=TwoWay}">
I agree.
</CheckBox>
<StackPanel
Margin="0,40,0,0"
HorizontalAlignment="Center"
Orientation="Horizontal">
<Button
x:Name="PART_Submit"
Width="80"
IsEnabled="{Binding ElementName=PART_UserConsent, Path=IsChecked}">
Submit
</Button>
<Button
x:Name="PART_Exit"
Width="80"
Margin="10,0,0,0">
Exit
</Button>
</StackPanel>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
GetService를 이용해서 Injection을 받기 위해서 App.xaml.cs는 아래와 같이 작업되어있습니다.
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddSingleton(typeof(MainWindow));
services.AddSingleton(typeof(MainWindowViewModel));
services.AddTransient(typeof(UserConsent));
services.AddTransient(typeof(UserConsentViewModel));
services.AddTransient(typeof(CustomUserConsent));
return services.BuildServiceProvider();
}
5. 실행
6. 소스
WpfTest/CustomControlSample at master · kaki104/WpfTest (github.com)
UserControl이나 CustomControl이나 출력 결과는 크게 다르지 않다는 것을 알 수 있습니다. 다만, 만드는 난이도는 약간의 차이를 보이고 있는데, 직접 만들어 보시고, 원하는 것으로 만드시면 될 것 같습니다.
반응형
'WPF .NET' 카테고리의 다른 글
Prism Library를 사용하는 개발자를 위한 안내 Part2 - 프로젝트 구성 살펴 보기 (2) | 2022.10.28 |
---|---|
Prism Library를 사용하는 개발자를 위한 안내 Part1 (0) | 2022.10.27 |
사용자 정의 컨트롤 만들기 Custom XAML Control - part1 (0) | 2022.10.06 |
MVVM Pattern을 사용하는 개발자를 위한 안내v1.0 part9-2 StyleSelector (0) | 2022.09.06 |
MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part9-1 DataTemplateSelector (3) | 2022.08.31 |
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- Cross-platform
- XAML
- Always Encrypted
- windows 11
- Windows 10
- #prism
- uno-platform
- IOT
- #uwp
- Build 2016
- .net 5.0
- visual studio 2019
- ComboBox
- dotNETconf
- PRISM
- Bot Framework
- MVVM
- UWP
- ef core
- Microsoft
- #MVVM
- .net
- #Windows Template Studio
- Behavior
- Visual Studio 2022
- kiosk
- C#
- uno platform
- LINQ
- WPF
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
글 보관함