카테고리 없음

[강좌] ImageButton 만들기.

알 수 없는 사용자 2008. 7. 15. 19:18

보통 버튼을 만들 때 이미지 두장 혹은 이미지 3장만을 가지고 Opacity를  조정해서
만들 때가 많았죠. 그래서 간단하게 ImageButton 을 만드는 방법을 알려드리려고 해요.

  이번 강좌는 너무너무 쉬워서 뭐 이런 걸 강좌라고 썼냐 하는 생각이 들지도 모르겠네요.

암튼 그래서 일단 빠르게 강좌를 진행하도록 하겠습니다.

  일단 버튼 이미지 세장을 준비하겠습니다.

  하나는 보통 상태의 이미지이고 또 하나는 MouseOver시의 이미지 그리고

나머지 하나는 Pressed 되었을 때의 이미지입니다. 뭐 Disabled 되었을 때의 이미지도

준비하고 싶으신분 들은 준비하셔도 됩니다.


 먼저 그냥 Button에 Style을 변경하여 만들어 보도록 하겠습니다.

일단 블랜드에서 Button 하나를 만듭니다.


 그 다음에는 그림의 오른쪽 위쪽에 있는 버튼을 눌러서 새로 스타일을 만듭니다.



Create Empty로 만들고 나면 다음과 같이 물어 봅니다.


    다음과 같이 Style을 새로 만들면

  아무것도 없고 Grid만 덜렁 하나 생겨 있을 것입니다.

그럼 그 안에 Image를 세장 넣고. 적절한 이름을 줍니다.

뭐 'NormalImage', 'OverImage', 'PressedImage'  정도가 되겠죠.

여기까지 끝냈으면 요렇게 나옵니다.

 크기에 따라 적당히 변할 수 있도록 Image의 Width,Height Property 값을 웬만하면 Auto로
주고 Stretch 값도 Uniform 이나 Fill 로 해둡니다.


  자 이제 State 를 줘 보도록 합시다.

  위쪽에 States 패널을 건드려 봅시다.  다음과 같이 있을 텐데요.


일단 기본 상태를 Normal로 두기 위해서 Normal Image를 제외한 다른 이미지의
Visibility를 Collapse로 해둡니다.

위 패널에서 MouseOver 를 클릭한 후에 OverImage를 제외한 나머지 이미지의
 Visibility를 Collapse로 해둡니다.

같은 식으로 Pressed를 클릭한 후에 PressedImage를 제외한 나머지 이미지의
 Visibility를 Collapse로 해둡니다.

쉽죠?^^

자 이제 돌아와서 실행을 시켜보죠.

여기까지의 소스코드는 다음과 같습니다.

Page.xaml
<UserControl x:Class="MakeImageButton.Page"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <Button Template="{StaticResource ImageButton}" Width="100" Height="50"/>
    </Grid>
</UserControl>

App.xaml
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="MakeImageButton.App"
             xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
             >
    <Application.Resources>
       
     <ControlTemplate x:Key="ImageButton" TargetType="Button">
      <Grid Background="#00000000">
       <vsm:VisualStateManager.VisualStateGroups>
        <vsm:VisualStateGroup x:Name="FocusStates">
         <vsm:VisualState x:Name="Unfocused"/>
         <vsm:VisualState x:Name="Focused"/>
        </vsm:VisualStateGroup>
        <vsm:VisualStateGroup x:Name="CommonStates">
         <vsm:VisualStateGroup.Transitions>
         </vsm:VisualStateGroup.Transitions>
         <vsm:VisualState x:Name="MouseOver">
          <Storyboard>
           <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="NormalImage" Storyboard.TargetProperty="(UIElement.Visibility)">
            <DiscreteObjectKeyFrame KeyTime="00:00:00">
             <DiscreteObjectKeyFrame.Value>
              <Visibility>Collapsed</Visibility>
             </DiscreteObjectKeyFrame.Value>
            </DiscreteObjectKeyFrame>
           </ObjectAnimationUsingKeyFrames>
           <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="OverImage" Storyboard.TargetProperty="(UIElement.Visibility)">
            <DiscreteObjectKeyFrame KeyTime="00:00:00">
             <DiscreteObjectKeyFrame.Value>
              <Visibility>Visible</Visibility>
             </DiscreteObjectKeyFrame.Value>
            </DiscreteObjectKeyFrame>
           </ObjectAnimationUsingKeyFrames>
          </Storyboard>
         </vsm:VisualState>
         <vsm:VisualState x:Name="Pressed">
          <Storyboard>
           <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="NormalImage" Storyboard.TargetProperty="(UIElement.Visibility)">
            <DiscreteObjectKeyFrame KeyTime="00:00:00">
             <DiscreteObjectKeyFrame.Value>
              <Visibility>Collapsed</Visibility>
             </DiscreteObjectKeyFrame.Value>
            </DiscreteObjectKeyFrame>
           </ObjectAnimationUsingKeyFrames>
           <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="PressedImage" Storyboard.TargetProperty="(UIElement.Visibility)">
            <DiscreteObjectKeyFrame KeyTime="00:00:00">
             <DiscreteObjectKeyFrame.Value>
              <Visibility>Visible</Visibility>
             </DiscreteObjectKeyFrame.Value>
            </DiscreteObjectKeyFrame>
           </ObjectAnimationUsingKeyFrames>
          </Storyboard>
         </vsm:VisualState>
         <vsm:VisualState x:Name="Disabled">
          <Storyboard/>
         </vsm:VisualState>
         <vsm:VisualState x:Name="Normal">
          <Storyboard/>
         </vsm:VisualState>
        </vsm:VisualStateGroup>
       </vsm:VisualStateManager.VisualStateGroups>
       <Image Height="Auto" HorizontalAlignment="Right" x:Name="NormalImage" VerticalAlignment="Bottom" Width="Auto" Source="Normal.png" Stretch="Fill"/>
       <Image Height="Auto" HorizontalAlignment="Right" x:Name="OverImage" VerticalAlignment="Bottom" Width="Auto" Visibility="Collapsed" Source="Over.png" Stretch="Fill"/>
       <Image Height="Auto" HorizontalAlignment="Right" x:Name="PressedImage" VerticalAlignment="Bottom" Width="Auto" Visibility="Collapsed" Source="Pressed.png" Stretch="Fill"/>
      </Grid>
     </ControlTemplate>
       
    </Application.Resources>
</Application>

어려울 것은 없죠.. 실제로는 다음과 같이 작동하겠죠.

Get Microsoft Silverlight


그런데.. 아무리 블랜드에서 작업하는게 편하다고 해도 이미지로 버튼을 만들 때 마다

이렇게 똑같은 작업을 반복한다고 하면 정말 지겨운 일이 아닐 수 없을 겁니다.

그냥 아래와 같이 프로퍼티만 셋팅하면 자동으로 이미지 3장으로 버튼을 만들 수 있으면

좋겠죠?

<ImageButton NormalImageSource="Normal.png" OverImageSource="Over.png" PressedImageSource="Pressed.png" />

  그래서 이제부터는 이렇게 간단하게 만들어 쓸 수 있는 ImageButton을 만들려고 합니다.

일단 기본적으로는 버튼하고 똑같고 위의 Style을 그대로 따르지만 Image의 Source만 다른 프로퍼티로 바꿀 수 있었으면 좋겠죠.

 예전의 짱묜님의 강좌에서는 이미지가 한장뿐이어서 Tag를 사용해서 해결했지만 이경우는
3개나 되니.. 그 방법은 쓰기가 힘들죠.

  그럼 일단 ImageButton을 만들어 보겠습니다. 간단히 class 를 하나 만드십쇼.

ImageButton이란 이름으로 말이죠. 그리고 Button을 상속 받습니다.

    public class ImageButton : Button
    {}


테스트 해보시면 알겠지만 이상태로도 바로 Button처럼 작동하게 됩니다.

Page.xaml
<UserControl x:Class="MakeImageButton.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:MakeImageButton;assembly=MakeImageButton"
    Width="135" Height="37">
    <Grid x:Name="LayoutRoot">  
        <my:ImageButton>    
    </Grid>
</UserControl>

이렇게만 해보아도(물론 자기 클래스를 my란 이름의 네임스페이스로 선언했을 때죠.) Button 과 똑같이 작동한다는 것을 알 수 있을 것입니다.

  이제 우리가 원하는 프로퍼티를 추가해야 할 때입니다.

  일단 위에서 언급한 NormalImageSource, OverImageSource, PressedImageSource 등은
xaml에서 다룰 수 있어야 하고 다른 Template의 값들과 Binding 이 가능하여야 하죠.

 이런 조건을 충족시켜주기 위해서는 DependencyProperty로 등록을 하여야만 합니다.

여기 쉽게 DependencyProperty를 추가해줄 수 있는 snippet이 있습니다.


유용하게 쓰시고 그럼 이걸 이용해서

프로퍼티 3가지를 추가 합니다. 여기서  Property의 Type은 ImageSource로 해줍니다.

그럼 다음과 같은 코드가 추가 될 것입니다.

        #region NormalImageSource
        /// <summary>
        /// Gets or sets the NormalImageSource possible Value of the Uri object.
        /// </summary>
        public ImageSource NormalImageSource
        {
            get { return (ImageSource)GetValue(NormalImageSourceProperty); }
            set { SetValue(NormalImageSourceProperty, value); }
        }
        /// <summary>
        /// Identifies the NormalImageSource dependency property.
        /// </summary>
        public static readonly DependencyProperty NormalImageSourceProperty =
                    DependencyProperty.Register(
                          "NormalImageSource",
                          typeof(ImageSource),
                          typeof(ImageButton),
                           new PropertyMetadata(OnNormalImageSourcePropertyChanged));
        /// <summary>
        /// NormalImageSourceProperty property changed handler.
        /// </summary>
        /// <param name="d">ImageButton that changed its NormalImageSource.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param>
        private static void OnNormalImageSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ImageButton _ImageButton = d as ImageButton;
            if (_ImageButton != null)
            {
                
            }
        }
        #endregion NormalImageSource
        #region OverImageSource
        /// <summary>
        /// Gets or sets the OverImageSource possible Value of the Uri object.
        /// </summary>
        public ImageSource OverImageSource
        {
            get { return (ImageSource)GetValue(OverImageSourceProperty); }
            set { SetValue(OverImageSourceProperty, value); }
        }
        /// <summary>
        /// Identifies the OverImageSource dependency property.
        /// </summary>
        public static readonly DependencyProperty OverImageSourceProperty =
                    DependencyProperty.Register(
                          "OverImageSource",
                          typeof(ImageSource),
                          typeof(ImageButton),
                           new PropertyMetadata(OnOverImageSourcePropertyChanged));
        /// <summary>
        /// OverImageSourceProperty property changed handler.
        /// </summary>
        /// <param name="d">ImageButton that changed its OverImageSource.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param>
        private static void OnOverImageSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ImageButton _ImageButton = d as ImageButton;
            if (_ImageButton != null)
            {
                //TODO: Handle new value.
            }
        }
        #endregion OverImageSource
        #region PressedImageSource
        /// <summary>
        /// Gets or sets the PressedImageSource possible Value of the Uri object.
        /// </summary>
        public ImageSource PressedImageSource
        {
            get { return (ImageSource)GetValue(PressedImageSourceProperty); }
            set { SetValue(PressedImageSourceProperty, value); }
        }
        /// <summary>
        /// Identifies the PressedImageSource dependency property.
        /// </summary>
        public static readonly DependencyProperty PressedImageSourceProperty =
                    DependencyProperty.Register(
                          "PressedImageSource",
                          typeof(ImageSource),
                          typeof(ImageButton),
                           new PropertyMetadata(OnPressedImageSourcePropertyChanged));
        /// <summary>
        /// PressedImageSourceProperty property changed handler.
        /// </summary>
        /// <param name="d">ImageButton that changed its PressedImageSource.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param>
        private static void OnPressedImageSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ImageButton _ImageButton = d as ImageButton;
            if (_ImageButton != null)
            {
               
            }
        }
        #endregion PressedImageSource

On어쩌구PropertyChanged 함수는 null 값을 넣어주고 구지 구현하지 않아도 상관없는 부분입니다.

  그러면 이제 ImageButton에 맞는 Default Style을 준비할 차례입니다.

먼저 xml 화일이나 cs화일이 없는 xaml 화일을 만듭니다. 그리고 이름을 generic.xaml

로 줍니다. 반드시 ImageButton 이 있는 프로젝트의 루트 부분에 있어야 합니다.

  그리고 generic.xaml 의 Build Action 을 Resource로 해주어야 합니다.

  자 그러면 이제 내용을 살펴 볼까요.

<ResourceDictionary
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
     xmlns:my="clr-namespace:MakeImageButton;assembly=MakeImageButton">
</ResourceDictionary>

요정도가 될 것같습니다.

여기에 Button의 기본적인 프로퍼티값을 setter로 셋팅해주고
Template 부분은 요전에 만들어 놨던 Button Template 값을 그대로 대체시켜줍니다.
거기에 Key값은 없애고 TargetType="my:ImageButton" 로 둡니다.

여기까지 잘 따라오셨다면 다음과 같은 코드가 생깁니다.
 <!--ImageButton-->
 <Style TargetType="my:ImageButton">
  <Setter Property="IsEnabled" Value="true" />
  <Setter Property="IsTabStop" Value="true" />
  <Setter Property="Background" Value="#00000000" />
  <Setter Property="Foreground" Value="#FF313131" />
  <Setter Property="MinWidth" Value="5" />
  <Setter Property="MinHeight" Value="5" />
  <Setter Property="Margin" Value="0" />
  <Setter Property="HorizontalContentAlignment" Value="Center" />
  <Setter Property="VerticalContentAlignment" Value="Center" />
  <Setter Property="Cursor" Value="Arrow" />
  <Setter Property="TextAlignment" Value="Left" />
  <Setter Property="TextWrapping" Value="NoWrap" />
  <Setter Property="FontSize" Value="11" />
  <Setter Property="Template">
   <Setter.Value>
                <ControlTemplate TargetType="my:ImageButton">
                    <Grid Background="#00000000">
                        <vsm:VisualStateManager.VisualStateGroups>
                            <vsm:VisualStateGroup x:Name="FocusStates">
                                <vsm:VisualState x:Name="Unfocused"/>
                                <vsm:VisualState x:Name="Focused"/>
                            </vsm:VisualStateGroup>
                            <vsm:VisualStateGroup x:Name="CommonStates">
                                <vsm:VisualStateGroup.Transitions>
                                </vsm:VisualStateGroup.Transitions>
                                <vsm:VisualState x:Name="MouseOver">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="NormalImage" Storyboard.TargetProperty="(UIElement.Visibility)">
                                            <DiscreteObjectKeyFrame KeyTime="00:00:00">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Collapsed</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="OverImage" Storyboard.TargetProperty="(UIElement.Visibility)">
                                            <DiscreteObjectKeyFrame KeyTime="00:00:00">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </vsm:VisualState>
                                <vsm:VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="NormalImage" Storyboard.TargetProperty="(UIElement.Visibility)">
                                            <DiscreteObjectKeyFrame KeyTime="00:00:00">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Collapsed</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="PressedImage" Storyboard.TargetProperty="(UIElement.Visibility)">
                                            <DiscreteObjectKeyFrame KeyTime="00:00:00">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </vsm:VisualState>
                                <vsm:VisualState x:Name="Disabled">
                                    <Storyboard/>
                                </vsm:VisualState>
                                <vsm:VisualState x:Name="Normal">
                                    <Storyboard/>
                                </vsm:VisualState>
                            </vsm:VisualStateGroup>
                        </vsm:VisualStateManager.VisualStateGroups>
                        <Image Height="Auto" HorizontalAlignment="Right" x:Name="NormalImage" VerticalAlignment="Bottom" Width="Auto" Source="Normal.png" Stretch="Fill"/>
                        <Image Height="Auto" HorizontalAlignment="Right" x:Name="OverImage" VerticalAlignment="Bottom" Width="Auto" Visibility="Collapsed" Source="Over.png" Stretch="Fill"/>
                        <Image Height="Auto" HorizontalAlignment="Right" x:Name="PressedImage" VerticalAlignment="Bottom" Width="Auto" Visibility="Collapsed" Source="Pressed.png" Stretch="Fill"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
  </Setter>
 </Style>

이제 다시 ImageButton.cs 로 돌아와서 생성자를 만들어줍니다.

        public ImageButton()
        {
              DefaultStyleKey = typeof(ImageButton);
        }

여기까지만 해도 일단 집어넣은 이미지가 있으니 작동을 하지요.

그런데 여기서 우리가 원하는 것은 프로퍼티 값을 바꾸어서 얼마든지 Image를 바꿀 수 있게
하는 것이죠. 자 그럼 Template 부분을 이렇게 바꾸어 봅시다.

Image Height="Auto" HorizontalAlignment="Right" x:Name="NormalImage" VerticalAlignment="Bottom" Width="Auto" Source="Normal.png" Stretch="Fill"/>
                        <Image Height="Auto" HorizontalAlignment="Right" x:Name="OverImage" VerticalAlignment="Bottom" Width="Auto" Visibility="Collapsed" Source="Over.png" Stretch="Fill"/>
                        <Image Height="Auto" HorizontalAlignment="Right" x:Name="PressedImage" VerticalAlignment="Bottom" Width="Auto" Visibility="Collapsed" Source="Pressed.png" Stretch="Fill"/>


                                                            ↓
<Image x:Name="NormalImage" Source="{TemplateBinding NormalImageSource}" Stretch="Fill"/>
<Image x:Name="OverImage" Source="{TemplateBinding OverImageSource}" Stretch="Fill" Visibility="Collapsed"/>
<Image x:Name="PressedImage" Source="{TemplateBinding PressedImageSource}" Stretch="Fill" Visibility="Collapsed"/>

   
  쓸데없는 프로퍼티들은 일부러 제거 했습니다.

그리고 이제 우리가 애초에 원하던 대로 코드를 바꾸어 봅시다.

Page.xaml
<UserControl x:Class="MakeImageButton.Page"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:MakeImageButton;assembly=MakeImageButton"
    Width="135" Height="37">
    <Grid x:Name="LayoutRoot">  
        <my:ImageButton NormalImageSource="Normal.png" OverImageSource="Over.png" PressedImageSource="Pressed.png">    
    </Grid>
</UserControl>

잘 작동하시나요?

Stretch 등의 속성도 추가하여 Image Stretch 형태도 바꾸어 줄 수 있습니다.

소스코드는 아래에 첨부합니다.


압축을 푼 뒤 한번 빌드 후에 실행하시면 될겁니다.

주의하셔할 것 몇가지를 언급하자면 ImageSource는 절대 두번이상 셋팅 될 수 없습니다.

만약 두 번 이상 셋팅 해주시려고 한다면 Property의 Get 함수에서 BitmapImage 을 새로

생성해서 반환해주는 코드를 짜주어야 합니다.

        public ImageSource NormalImageSource
        {
            get { return new BitmapImage(new Uri((GetValue(NormalImageSourceProperty) as BitmapImage).UriSource.OriginalString, UriKind.RelativeOrAbsolute)); }
            set { SetValue(NormalImageSourceProperty, value); }
        }

  뭐 이정도 소스가 되겠죠..^^;;

 
  이렇게 글로 설명하는데는 별로 익숙하지 못해서 잘 이해가 되셨는지 모르겠군요.

자 그럼 이제 Custom Control의 세계로...^^~~~

                                                                                          - smile -