[WPF] ControlTemplate ์ ์šฉ

ControlTemplate์ด ์ ์šฉ๋˜๋Š” ์›๋ฆฌ๋ฅผ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค.
DevExpress์— ๋ฌธ์˜ํ•ด์„œ ๋ฐ›์€ ์ƒ˜ํ”Œ์ธ๋ฐโ€ฆ์ด ์ƒ˜ํ”Œ๊ณผ ๋˜‘๊ฐ™์ด ์ˆ˜ํ–‰ํ•ด๋„ ์ œ ์†Œ์Šค์—์„œ๋Š” ์„ ํƒ ์‹œ ํ…Œ๋‘๋ฆฌ๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š๋„ค์š”.

<Window
    x:Class="DXSample.NetCore.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:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
    xmlns:dxgt="http://schemas.devexpress.com/winfx/2008/xaml/grid/themekeys"
    xmlns:local="clr-namespace:DXSample.NetCore"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    UseLayoutRounding="True"
    mc:Ignorable="d">

    <Window.Resources>
        <ControlTemplate x:Key="{dxgt:GridCardThemeKey ResourceKey=ContainerTemplate, ThemeName=Office2019Colorful}" TargetType="{x:Type ContentControl}">
            <Grid x:Name="Root" Background="Transparent">
                <Border
                    x:Name="IsDefault"
                    Background="#FFF8F8F8"
                    BorderBrush="#FFABABAB"
                    BorderThickness="1">
                    <ContentPresenter />
                </Border>
            </Grid>
            <ControlTemplate.Triggers>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="IsMouseOver" Value="True" />
                        <Condition Property="dxg:GridViewBase.IsFocusedRow" Value="False" />
                    </MultiTrigger.Conditions>
                </MultiTrigger>
                <DataTrigger Binding="{Binding Path=(dxg:RowData.RowData).SelectionState, RelativeSource={RelativeSource TemplatedParent}}" Value="Focused">
                    <Setter TargetName="IsDefault" Property="BorderBrush" Value="Red" />
                </DataTrigger>
                <DataTrigger Binding="{Binding Path=(dxg:RowData.RowData).SelectionState, RelativeSource={RelativeSource TemplatedParent}}" Value="Selected">
                    <Setter TargetName="IsDefault" Property="BorderBrush" Value="Red" />
                </DataTrigger>
                <Trigger Property="dxg:GridViewBase.IsFocusedRow" Value="True" />
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Resources>

    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>

    <DockPanel>
        <Button Command="{Binding SelectCommand}" DockPanel.Dock="Bottom">
            Select
        </Button>
        <dxg:GridControl
            AutoGenerateColumns="AddNew"
            ItemsSource="{Binding Items}"
            SelectedItems="{Binding SelectedItems}"
            SelectionMode="Row">
            <dxg:GridControl.View>
                <dxg:CardView />
            </dxg:GridControl.View>
        </dxg:GridControl>
    </DockPanel>

</Window>
using System.Windows;
using System.Collections.ObjectModel;
using DevExpress.Mvvm;
using DevExpress.Mvvm.DataAnnotations;

namespace DXSample.NetCore {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }
    }
    public class MainViewModel : ViewModelBase {
        public ObservableCollection<Item> Items { get => GetValue<ObservableCollection<Item>>(); set => SetValue(value); }
        public ObservableCollection<Item> SelectedItems { get => GetValue<ObservableCollection<Item>>(); set => SetValue(value); }
        public MainViewModel() {
            Items = new ObservableCollection<Item>();
            for (int i = 0; i < 100; i++) {
                var item = new Item { Id = i, Name = string.Format("Name_{0}", i) };
                Items.Add(item);
            }
            SelectedItems = new ObservableCollection<Item>();
        }
        [Command]
        public void Select() {
            SelectedItems.Clear();
            for (int i = 0; i < 10; i++)
                SelectedItems.Add(Items[i]);
        }
    }
    public class Item {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

์†Œ์Šค์ฒจ๋ถ€๊ฐ€ ๋˜์ง€ ์•Š์•„ ๋ณต๋ถ™์œผ๋กœ ๋‚จ๊น๋‹ˆ๋‹ค. ์œ„์˜ ์†Œ์Šค๊ฐ€ ์ƒ˜ํ”Œ์˜ ์ „๋ถ€์ด๋ฏ€๋กœ, MainWindow.xamlํ•˜๊ณ  ์ฝ”๋“œ๋น„ํ•˜์ธ๋“œ์— ๋ถ™์—ฌ๋„ฃ์œผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์‹คํ–‰ํ•˜์‹œ๊ณ  Visual Tree ์ผœ๋ณด์‹œ๋ฉด CardView์˜ Item์œผ๋กœ ControlTemplate์œผ๋กœ ์ •์˜ํ•œ ์š”์†Œ๋“ค์ด ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ DevExpress์˜ ๊ธฐ์ˆ ์ผ๊นŒ์š”? WPF์—์„œ ๋ชจ๋“  ์ปจํŠธ๋กค์€ ContentControl, ItemsControl ๋‘˜ ์ค‘ํ•˜๋‚˜๋ฅผ ์ƒ์†๋ฐ›์•„ ๊ตฌํ˜„๋œ ๋‹ค๋Š” ๊ฒƒ์€ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์™œ TargetType์œผ๋กœ ContentControl์„ ์ง€์ •ํ–ˆ๋Š”๋ฐ CardView์˜ Item์—๋งŒ ์ƒ๊ธฐ๋Š” ๊ฒƒ์ผ๊นŒ์š”?

๊ทธ๋ฆฌ๊ณ  ํ˜น์‹œโ€ฆ์ œ PC์— ์ด๊ฑธ ์™œ ์ ์šฉํ•ด๋„ ์•ˆ๋˜๋Š”์ง€ ํ˜น์‹œ ๋ด์ฃผ์‹ค ๋ถ„์ด ๊ณ„์‹œ๋‹ค๋ฉด LiveShare๋„ ์š”์ฒญ๋“œ๋ฆฝ๋‹ˆ๋‹คโ€ฆใ…Žใ…Ž

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

์ข‹์•„์š” 1

@Vincent ์ƒ˜ํ”Œ์€ ์ž˜ ๋™์ž‘ํ•˜๋Š”๋ฐ ์˜ฎ๊ธฐ๋ฉด ์ ์šฉ์ด ์•ˆ๋œ๋‹ค๋Š” ๋ง์”€์ด์‹ ๊ฐ€์š”?

์ง‘์— ๊ฐ€์„œ ํ•œ๋ฒˆ ํ•ด๋ณผ๊ฒŒ์š”~

๋„ค ๊ทธ๋ฆฌ๊ณ  Border, Panel ๋นผ๊ณ ๋Š” ๋ชจ๋‘ Control์„ ์ƒ์†๋ฐ›๋Š”๋ฐ
ContentControl ๋˜๋Š” ItemsControl์—์„œ virutal๋กœ ์ œ๊ณตํ•˜๋Š” Template ์…€๋ ‰ํŒ… ๊ด€๋ จ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์–ด์„œ
DevExpress๋„ ์ด๋ฅผ Override ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ถ€๋ถ„์ด ์žˆ์„์ˆ˜๋„ ์žˆ์–ด์š”. ์•„๋งˆ ํ…Œ๋งˆ? ๊ด€๋ จํ•ด์„œ ๋ฌด์–ธ๊ฐ€ ์ฒ˜๋ฆฌํ•  ์ˆ˜๋„ ์žˆ๊ณ โ€ฆ ์„ค๋ช…์ด ์ข€ ๋ถ€์กฑํ•˜๋„ค์š”. :joy:

์ง‘์—๊ฐ€์„œ ํ•ด๋ณด๋ฉด์„œ ๋ณด๋ฉด ์ •ํ™•ํžˆ ์„ค๋ช…๋“œ๋ฆด ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์€๋ฐ ๋ฐค์— ๋” ๋ณด์™„ํ•ด๋ณผ๊ฒŒ์š”.

์ข‹์•„์š” 1

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!! ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค ใ…Žใ…Ž

์ข‹์•„์š” 1

@Vincent ์ƒ˜ํ”Œ์†Œ์Šค๋Š” ์ •์ƒ ๋™์ž‘ ํ•˜๋Š” ๊ฒƒ ๊ฐ™๋„ค์š”.

๊ทธ๋Ÿฐ๋ฐ dxgt:GridCardThemeKey ํด๋ž˜์Šค ์ ‘๊ทผ์ด ์•ˆ๋ฉ๋‹ˆ๋‹ค.
๋Ÿฐํƒ€์ž„์—์„œ๋Š” ๋™์ž‘ํ•˜์ง€๋งŒ ์–ด๋–ค ์–ด์…ˆ๋ธ”๋ฆฌ๋ฅผ ์ฐธ์กฐํ•ด์•ผ GridCardThemeKey๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋Š”์ง€ ๋ชจ๋ฅด๊ฒ ๋„ค์š”.

๊ทธ๋ฆฌ๊ณ  GridCardThemeKey์€ MarkupExtension๋ฅผ ์ƒ์†๋ฐ›์€ ํด๋ž˜์Šค์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ResourceKey, THemeName ๊ฐ’์„ ํ†ตํ•ด ๋‚ด๋ถ€์ ์œผ๋กœ Template์„ ์ ์šฉํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

public class GridCardThemeKey : MarkupExtension
{
     public string ResourceKey { get; set; }
     public string ThemeName { get; set; }
    
     ...
}

๊ทธ๋ฆฌ๊ณ  ์ ์šฉ์ด ์•ˆ๋˜๋Š” ๊ฒƒ์€ ์•„๋งˆ ๋ฆฌ์†Œ์Šค ์œ„์น˜์— ๋ฌธ์ œ๊ฐ€ ์žˆ์ง€ ์•Š๋‚˜ ์‹ถ์–ด์š”.

์ข‹์•„์š” 1

์•„ํ•˜โ€ฆ๊ทธ๋ ‡๋‹ค๋ฉด DevExpress์ธก์— ๋ฌธ์˜๋ฅผ ํ•ด๋ด์•ผ๊ฒ ๊ตฐ์š”โ€ฆ๋„๋Œ€์ฒด ์–ด๋–ป๊ฒŒ ์ ์šฉ์‹œํ‚ค๋Š”๊ฑด์ง€ ๋ชจ๋ฅด๊ฒ ์–ด์„œโ€ฆ ํ…Œ์ŠคํŠธ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค ์ œ์ž„์Šค๋‹˜!

์ข‹์•„์š” 1

@Vincent ์•— ์ €๋„ ๊ทธ๋ž˜์„œ GridCardThemeKey ๊ด€๋ จํ•ด์„œ ํ‹ฐ์ผ“ ๋“ฑ๋กํ–ˆ๋Š”๋ฐ
https://supportcenter.devexpress.com/ticket/details/t1007987/in-which-assembly-is-gridcardthemekey-included

๋งํฌ๊ฐ€ ์•ˆ๋ณด์ด๋„ค์š”.

์•„๋งˆ ๋ฐ๋ธŒ์ต์Šคํ”„๋ ˆ์Šค๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํ”„๋ผ์ด๋น— ์งˆ๋ฌธ ํ‹ฐ์ผ“์ด๊ณ  ์†”๋ฃจ์…˜ ๋‹ต๋ณ€์ด ๋‹ฌ๋ฆฌ๋ฉด ํ•˜๋‹จ์— public์œผ๋กœ ๋Œ๋ฆฌ๋Š” ๊ธฐ๋Šฅ์ด ๋”ฐ๋กœ ์žˆ์œผ์…”์„œ ํ˜„์žฌ ํ”„๋ผ์ด๋น— ์ƒํƒœ์‹ ๊ฑฐ ๊ฐ™์Šต๋‹ˆ๋‹ค ใ…Žใ…Ž

์ข‹์•„์š” 1

@Vincent ๋‹ค์‹œ ์งˆ๋ฌธ์˜ฌ๋ ธ๋Š”๋ฐ ๋‹ต๋ณ€ ๋‹ฌ๋ฆฌ๋ฉด ๊ณต์œ ํ•ด๋ณผ๊ฒŒ์š”!!

์ข‹์•„์š” 1

์ด๋Ÿฐ ๋งํฌ๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค ใ…Žใ…Ž ์ €๋ž‘ ๋น„์Šทํ•œ ๊ฒฝ์šฐ์ธ๊ฑฐ ๊ฐ™์Šต๋‹ˆ๋‹ค