Wpf xaml에서 많은 버튼들을 어떻게 배치하나요? + dependency Property 질문

저는 WPF로 주기율표를 만드려고 해서 page를 하나 만들어서 거기에 버튼들을 배치하고 싶습니다. 근데 버튼이 100개가 넘어가는데 그것을 다 margin으로 배치해야 하는것인가요?
image
이런식으로 버튼들을 배치하고 싶습니다.
아 그리고 다른 질문이 하나 더 있는데 수많은 버튼에다가 이미지를 다 씌울 수 없으니까 사용자 정의 컨트롤을 하나 만들어서 거기에 속성값을 추가해서 여러번 쓰고 싶습니다. 근데 찾아보니까 dependency Property라는 것이 있더라고요… 근데 예시가 다 text로 되어있던데 저는 backGround, cornerRadius 등등 다른 속성 값도 추가해보고 싶은데 어떻게 해야 하나요??
image
xaml코드는 이렇게 했습니다.

3개의 좋아요

네. Margin으로 컨트롤 간격을 결정할 수 있습니다. 그런데 어떤 레이아웃을 사용 하느냐에 따라 달라질 수 있습니다. 가령 전체 사이즈에서 버튼을 가변 사이즈로 배치하고 싶을 때는 Gird의 RowDefinitions, ColumnDefinition에 높이, 너비를 "*"로 주면 가능합니다.

<Window x:Class="WpfApp17.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:WpfApp17"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" Background="#353040">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <local:ElementControl Grid.Row="0" Grid.Column="0" Weight="1.00" ElementName="Hydrogen">H</local:ElementControl>
        <local:ElementControl Grid.Row="0" Grid.Column="17">He</local:ElementControl>

        <local:ElementControl Grid.Row="1" Grid.Column="0">Li</local:ElementControl>
        <local:ElementControl Grid.Row="1" Grid.Column="1">Be</local:ElementControl>
        <local:ElementControl Grid.Row="1" Grid.Column="12">B</local:ElementControl>
        <local:ElementControl Grid.Row="1" Grid.Column="13">C</local:ElementControl>
        <local:ElementControl Grid.Row="1" Grid.Column="14">N</local:ElementControl>
        <local:ElementControl Grid.Row="1" Grid.Column="15">O</local:ElementControl>
        <local:ElementControl Grid.Row="1" Grid.Column="16">F</local:ElementControl>
        <local:ElementControl Grid.Row="1" Grid.Column="17">Ne</local:ElementControl>

        <local:ElementControl Grid.Row="2" Grid.Column="0">Na</local:ElementControl>
        <local:ElementControl Grid.Row="2" Grid.Column="1">Mg</local:ElementControl>

        <!-- .. -->
    </Grid>
</Window>

자신이 만든 컨트롤에 다양한 속성을 부여하려면 말한 대로 Dependency Property를 구현하면 됩니다.

    public class ElementControl : ContentControl
    {
        public static DependencyProperty WeightProperty = DependencyProperty.Register("Weight", typeof(string), typeof(ElementControl), new PropertyMetadata());
        public static DependencyProperty ElementNameProperty = DependencyProperty.Register("ElementName", typeof(string), typeof(ElementControl), new PropertyMetadata());

        public string Weight { get => (string)GetValue(WeightProperty); set => SetValue(WeightProperty, value); }
        public string ElementName {  get => (string)GetValue(ElementNameProperty); set => SetValue(ElementNameProperty, value);}


        static ElementControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ElementControl), new FrameworkPropertyMetadata(typeof(ElementControl)));
        }
    }

위의 구현은 ContentControl을 상속받아 Content를 그대로 사용하고 있습니다.

아래는 해당 컨트롤의 스타일입니다.

| Thmes\Generic.xaml

    <Style TargetType="{x:Type local:ElementControl}">
        <Setter Property="Margin" Value="2" />
        <Setter Property="Padding" Value="0" />
        <Setter Property="Background" Value="White" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ElementControl}">
                    <Border Background="{TemplateBinding Background}" Margin="{TemplateBinding Margin}" Padding="{TemplateBinding Padding}" CornerRadius="10" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                        <Viewbox>
                            <Grid Width="100" Height="100">
                                <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="15" Margin="5,0,0,0" Text="{TemplateBinding Weight}" />
                                <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="40" FontFamily="Araial Rounded MT Bold">
                                    <ContentPresenter />
                                </TextBlock>
                                <TextBlock HorizontalAlignment="Center" VerticalAlignment="Bottom" FontSize="20"  FontFamily="Araial Rounded MT Bold" Margin="0,0,0,5" Text="{TemplateBinding ElementName}" />
                            </Grid>
                        </Viewbox>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

여기에서 Background는 기본 구현된 속성이여서 TemplateBinding으로 넘겨주면 되고, CornerRadiusContentControl에 없으므로 Dependency Property를 구현해서 Border에 넘겨줘도 됩니다.

그런데 어짜피 Border가 최상위 UI 구성이므로 저처럼 ContentControl에서 상속 받는게 아니라 그냥 Border에서 상속 받으면 CornerRadius를 바로 쓸 수 있습니다.

image

8개의 좋아요

와… 이렇게 자세하게 설명해주시다니 너무 감사합니다! 많은 도움이 되었습니다!!!

3개의 좋아요

^^; 열심히 하는 모습에 자세히 답변을 달아야겠다 싶었습니다. 계속 전진해서 멋진 프로그래머 되세요.

6개의 좋아요

눈여겨보던 글이었는데 학생이라는 …
정말 기대가 되는 프로그래머입니다.
winform에서 WPF로 넘어오기를 기대했는데 개인적으로 반갑습니다…

위 내용처럼 연습해 보고 더 나아가서는
model 만들고 ViewModel 생성해서
View에서 ItemsControl 이용하고 Panel은 WrapPanel 을 가로로 배치하면
위 내용은 간단하게 되리라 생각됩니다…
사이즈 또는 Blank 모델도 만들면 위 배치 그대로 구현 가능하다 생각됩니다.
https://cafe.naver.com/wince5/543
참고하면 도움이 될 것 같습니다.

5개의 좋아요

혹시 usercontrol과 contentcontrol의 차이점을 알 수 있을까요??

1개의 좋아요

차이점이 있습니다. 컨트롤은 다음의 상속 구조로 구성되는데요,

Object → DispatcherObject → DependencyObject → Visual → ULElement → FrameworkElement → Control → ContentControl → UserControl

UserControl은 하위 클래스의 기능을 가졌다 라고 이해하면 쉽습니다. 즉, UserControl 역시 ContentControl에서 제공하는 기능이 있습니다. 그런데, UserControl을 안쓰고 ContentControl을 썼는가 하면, 굳이 UserControl에서 제공하는 기능이 필요없을 때 그런 선택을 할 수 있어요.

가령, ContentControl에는 Content를 쓸 수 있도록 구현되어 있습니다. 그런데 그 상위 클래스인 Control은 Content가 없어서 Content를 쉽게 쓰고 싶을 때는 ContentControl을 선택하는 식입니다.

그렇다면 UserControl만의 기능이 무엇인지 살펴보면 선택 시 수월하겠지요.

    //
    // 요약:
    //     Provides a simple way to create a control.
    public class UserControl : ContentControl
    {
        //
        // 요약:
        //     Initializes a new instance of the System.Windows.Controls.UserControl class.
        public UserControl()
        {
        }

        //
        // 요약:
        //     Creates and returns an System.Windows.Automation.Peers.AutomationPeer for this
        //     System.Windows.Controls.UserControl.
        //
        // 반환 값:
        //     A new System.Windows.Automation.Peers.UserControlAutomationPeer for this System.Windows.Controls.UserControl.
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            throw null;
        }
    }

흠… (거의) 차이가 없네요 ^^; 어짜피 ContentControl이든 UserControl이든 XAML 디자이너의 도움을 받을 수 있으니 차이가 “없다고” 봐도 됩니다.

6개의 좋아요

UserControl, ContentControl의 큰 차이점은
Control의 속성을 사용하느냐 차이인 것 같습니다.
예를 들자면 Background 속성 같은 것들입니다.
오래되어서 가물 하네요.

3개의 좋아요

귀찮으실 수도 있는데… 질문 몇개만 더 드리자면 local:은 무슨 뜻이고 contentControl은 어떻게 생성하나요??

  1. local:은 xmlns(네임스페이스)를 의미하고 위 샘플에서는 d, x, mc, local이 이에 해당합니다.
  2. 상속의 정의인지, 컨트롤 생성인지요?
1개의 좋아요

저는 사용자 정의 컨트롤을 생성했는데 contentControl은 어떻게 생성하나요?
보통 public class 이름 : UserControl이라고 되어있는데 UserControl을 그냥 ContentControl로 바꾸면 되는 것인가요?

1개의 좋아요

UserControl을 만드는 것이라면 그냥 UserControl에서 상속 받아 만들면 됩니다 CustomControl로 만드는거라면 클래스 구현부와 스타일을 따로 만들게 됩니다. 추가 파일에 커스텀클래스 있으니까 그걸로 확인 해보면 됩니다.

3개의 좋아요

오래된 글에 댓글 달아버리기ㅋㅋㅋㅋㅋ

UserControl 과 CustomControl 은
Control 의 파생이나 아니면 한단계 더 나아가 ContetnControl 의 파생이냐 에 따라 구분됩니다만

이걸 이렇게 분리한 의도는 대략

Layout 을 구성하는 View → UserControl
Component 를 구성하는 View → CustomControl

이렇게 보시면 됩니다.

예를 들어서,
로그인 화면을 만들기 위해서는 UserControl 을 이용해 Layout 을 잡구요.
그 내부에 버튼 따위의 Component 를 구성할 때에는 CustomControl 을 사용합니다.

그래서 UserControl 은 Layout 구성을 위해 하위 요소가 필요하므로 ContentControl 을 상속받고
CustomControl 은 Component 처럼 사용하기 때문에 하위요소가 필요 없으므로 Control 만 상속받습니다.

물론 CustomControl 을 생성할 때 임의로 ContentControl 을 상속받아도 됩니다.
그럼 Layout 처럼 쓸 수 있는 거죠.
(제한은 없어요. 안 되는 게 어딨니ㅋㅅㅋ)

여기서 또 차이점은 ViewModel 의존적인 요소입니다.
Layout 은 그것을 정의할 때 이미 Binding 을 사용해 각 View 를 선언하고 ViewModel 과 연결하지요.
(물론 그렇지 않을 수도 있지만 보통은 그러하지요.)

그래서 보통 UserControl 의 View 작성은 ViewModel 의 요소를 요구하게 됩니다.
(Binding 이나 Command 등등의 것들에 ViewModel 의 property 이름 따위가 필요하겠죠? 물론 직접 적인 타입 참조는 없어야 하겠지만요.)

그러나 CustomControl 은 component 로 사용해야하기 때문에
현재 ROI 이외 영역에서도 다른 ViewModel 과 관계없이 사용할 수 있어야해요.
그렇기 때문에 CustomControl 자체의 구현에는 ViewModel 요소가 들어가지 않아야 하죠.
(물론 이것도 구현에 따라 다를 수 있어요.)

CustomControl 의 DP 는 CustomControl 을 가져다 쓰는 쪽에서 ViewModel 과 연결해서 사용합니다.


다시 정리하면

Layout 을 구성하는 View → UserControl
Component 를 구성하는 View → CustomControl

이렇게 알고 계시면 언제 어떤 View 를 만들어 써야할 지 감이 올거예요 ㅇㅅㅇb

5개의 좋아요