첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
본 블로그는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 블로그 콘텐츠 향상을 위해 쓰여집니다.

728x90
반응형
728x170

TestSolution.zip
다운로드

▶ Generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
    xmlns:local="clr-namespace:TestLibrary">
    <Style TargetType="{x:Type local:ListControl}">
        <Setter
            Property="Background"
            Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" />
        <Setter
            Property="BorderBrush"
            Value="#ff707070" />
        <Setter
            Property="BorderThickness"
            Value="1" />
        <Setter
            Property="KeyboardNavigation.TabNavigation"
            Value="Once" />
        <Setter
            Property="Padding"
            Value="3" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ListControl}">
                    <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <ScrollViewer x:Name="scrollViewer"
                            Padding="{TemplateBinding Padding}"
                            HorizontalScrollBarVisibility="Auto"
                            VerticalScrollBarVisibility="Auto">
                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style TargetType="{x:Type local:SelectorItem}">
        <Setter
            Property="HorizontalContentAlignment"
            Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:Selector}}, Path=HorizontalContentAlignment}" />
        <Setter
            Property="VerticalContentAlignment"
            Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:Selector}}, Path=VerticalContentAlignment}" />
        <Setter
            Property="Padding"
            Value="4 0 0 0" />
        <Setter
            Property="IsTabStop"
            Value="False" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:SelectorItem}">
                    <Border x:Name="backgroundBorder"
                        Margin="1"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <DockPanel
                            Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}, Mode=FindAncestor}, Path=ActualWidth}"
                            LastChildFill="True">
                            <BulletDecorator DockPanel.Dock="Left"
                                Margin="2 0 0 0"
                                VerticalAlignment="Center"
                                SnapsToDevicePixels="True"
                                Background="Transparent">
                                <BulletDecorator.Bullet>
                                    <themes:BulletChrome
                                        BorderBrush="Black"
                                        IsRound="{Binding Path=SingleSelect, RelativeSource={RelativeSource AncestorType={x:Type local:ListControl}, Mode=FindAncestor}}"
                                        IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}}"
                                        RenderMouseOver="{TemplateBinding IsMouseOver}" />
                                </BulletDecorator.Bullet>
                            </BulletDecorator>
                            <ContentControl
                                Margin="5 1 0 1"
                                VerticalAlignment="Center"
                                ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}"
                                ContentTemplate="{TemplateBinding ContentTemplate}"
                                Content="{TemplateBinding Content}"
                                Foreground="{TemplateBinding Foreground}" />
                        </DockPanel>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter
                                TargetName="backgroundBorder"
                                Property="Background"
                                Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
                            <Setter
                                Property="Foreground"
                                Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" />
                        </Trigger>
                        <Trigger Property="IsKeyboardFocusWithin" Value="true">
                            <Setter
                                TargetName="backgroundBorder"
                                Property="Background"
                                Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
                            <Setter
                                Property="Foreground"
                                Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

 

▶ ListControl.cs

using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace TestLibrary
{
    /// <summary>
    /// 리스트 컨트롤
    /// </summary>
    public class ListControl : Selector
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 단일 선택 의존 속성 - SingleSelectProperty

        /// <summary>
        /// 단일 선택 의존 속성
        /// </summary>
        public static readonly DependencyProperty SingleSelectProperty = DependencyProperty.Register
        (
            "SingleSelect",
            typeof(bool),
            typeof(ListControl),
            new UIPropertyMetadata(false, singleSelectPropertyChangedCallback)
        );

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// PREVIEW 마우스 DOWN 이벤트 핸들러
        /// </summary>
        private MouseButtonEventHandler previewMouseDownEventHandler = null;

        /// <summary>
        /// PREVIEW 마우스 UP 이벤트 핸들러
        /// </summary>
        private MouseButtonEventHandler previewMouseUpEventHandler = null;

        /// <summary>
        /// PREVIEW 마우스 이동시 이벤트 핸들러
        /// </summary>
        private MouseEventHandler previewMouseMoveEventHandler = null;

        /// <summary>
        /// 드래그 시작 인덱스
        /// </summary>
        private int dragStartIndex = -1;

        /// <summary>
        /// 드래그 종료 인덱스
        /// </summary>
        private int dragEndIndex = -1;

        /// <summary>
        /// 스크롤 뷰어
        /// </summary>
        private ScrollViewer scrollViewer = null;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 단일 선택 - SingleSelect

        /// <summary>
        /// 단일 선택
        /// </summary>
        public bool SingleSelect
        {
            get
            {
                return (bool)GetValue(SingleSelectProperty);
            }
            set
            {
                SetValue(SingleSelectProperty, value);
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Static

        #region 생성자 - CheckListBox()

        /// <summary>
        /// 생성자
        /// </summary>
        static ListControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ListControl), new FrameworkPropertyMetadata(typeof(ListControl)));
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - CheckListBox()

        /// <summary>
        /// 생성자
        /// </summary>
        public ListControl()
        {
            this.previewMouseDownEventHandler = new MouseButtonEventHandler(ItemsControl_PreviewMouseDown);
            this.previewMouseUpEventHandler   = new MouseButtonEventHandler(ItemsControl_PreviewMouseUp  );
            this.previewMouseMoveEventHandler = new MouseEventHandler      (ItemsControl_PreviewMouseMove);


            AddHandler(ItemsControl.PreviewMouseDownEvent, this.previewMouseDownEventHandler);
            AddHandler(ItemsControl.PreviewMouseUpEvent  , this.previewMouseUpEventHandler  );
            AddHandler(ItemsControl.PreviewMouseMoveEvent, this.previewMouseMoveEventHandler);

            Loaded += Selector_Loaded;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 단일 선택 속성 변경시 콜백 처리하기 - singleSelectPropertyChangedCallback(dependencyObject, e)

        /// <summary>
        /// 단일 선택 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void singleSelectPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ListControl checkListBox = d as ListControl;

            bool newValue = (bool)e.NewValue;

            if(newValue)
            {
                checkListBox.surpressItemSelectionChanged = true;

                IList selectedList = checkListBox.SelectedItems;

                if(selectedList != null)
                {
                    if(selectedList.Count > 1)
                    {
                        IList sourceList = (checkListBox.ItemsSource ?? checkListBox.Items) as IList;

                        for(int i = 0; i < sourceList.Count; i++)
                        {
                            object item = sourceList[i];

                            SelectorItem selectorItem = checkListBox.ItemContainerGenerator.ContainerFromItem(item) as SelectorItem;

                            selectorItem.IsSelected = false;
                        }
                    }
                }

                checkListBox.surpressItemSelectionChanged = false;

                ItemSelectionChangedEventArgs itemSelectionChangedEventArgs = new ItemSelectionChangedEventArgs
                (
                    Selector.ItemSelectionChangedEvent,
                    checkListBox,
                    null,
                    true
                );

                checkListBox.RaiseEvent(itemSelectionChangedEventArgs);
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 셀렉터 로드시 처리하기 - Selector_Loaded(sender, e)

        /// <summary>
        /// 셀렉터 로드시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void Selector_Loaded(object sender, RoutedEventArgs e)
        {
            this.scrollViewer = Template.FindName("scrollViewer", this) as ScrollViewer;
        }

        #endregion

        #region 항목들 컨트롤 PREVIEW 마우스 DOWN 처리하기 - ItemsControl_PreviewMouseDown(sender, e)

        /// <summary>
        /// 항목들 컨트롤 PREVIEW 마우스 DOWN 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void ItemsControl_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if(e.ChangedButton == MouseButton.Left)
            {
                if(Keyboard.Modifiers == ModifierKeys.Control || Keyboard.Modifiers == ModifierKeys.Shift)
                {
                    return;
                }

                IList sourceList = (ItemsSource ?? Items) as IList;

                if(sourceList  == null || sourceList.Count == 0)
                {
                    return;
                }

                HitTestResult hitTestResult = VisualTreeHelper.HitTest(this, e.GetPosition(this));

                if(hitTestResult == null)
                {
                    return;
                }

                SelectorItem selectorItem = hitTestResult.VisualHit.FindParent<SelectorItem>();

                if(selectorItem == null)
                {
                    return;
                }

                object item = selectorItem.Content as object;

                if(item == null)
                {
                    return;
                }

                this.dragStartIndex = sourceList.IndexOf(item);
            }
        }

        #endregion
        #region 항목들 컨트롤 PREVIEW 마우스 이동시 처리하기 - ItemsControl_PreviewMouseMove(sender, e)

        /// <summary>
        /// 항목들 컨트롤 PREVIEW 마우스 이동시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void ItemsControl_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if(e.LeftButton == MouseButtonState.Pressed)
            {
                Point mousePosition = e.GetPosition(this);

                if(this.scrollViewer != null)
                {
                    if(mousePosition.Y > Height - 8)
                    {
                        this.scrollViewer.ScrollToVerticalOffset(this.scrollViewer.VerticalOffset + 4);
                    }
                    else if(mousePosition.Y < 8)
                    {
                        this.scrollViewer.ScrollToVerticalOffset(this.scrollViewer.VerticalOffset - 4);
                    }
                }
            }
        }

        #endregion
        #region 항목들 컨트롤 PREVIEW 마우스 UP 처리하기 - ItemsControl_PreviewMouseUp(sender, e)

        /// <summary>
        /// 항목들 컨트롤 PREVIEW 마우스 UP 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void ItemsControl_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if(e.ChangedButton == MouseButton.Left)
            {
                if(Keyboard.Modifiers == ModifierKeys.Control || Keyboard.Modifiers == ModifierKeys.Shift)
                {
                    return;
                }

                IList sourceList = (ItemsSource ?? Items) as IList;

                if(sourceList  == null || sourceList.Count == 0)
                {
                    return;
                }

                HitTestResult hitTestResult = VisualTreeHelper.HitTest(this, e.GetPosition(this));

                if(hitTestResult == null)
                {
                    return;
                }

                SelectorItem selectorItemHitted = hitTestResult.VisualHit.FindParent<SelectorItem>();

                if(selectorItemHitted == null)
                {
                    return;
                }

                object itemHitted = selectorItemHitted.Content as object;

                if(itemHitted == null)
                {
                    return;
                }

                this.dragEndIndex = sourceList.IndexOf(itemHitted);

                if(this.dragStartIndex == -1 || this.dragEndIndex == -1)
                {
                    return;
                }

                if(SingleSelect)
                {
                    this.dragStartIndex = this.dragEndIndex;
                }
                else
                {
                    if(this.dragStartIndex > this.dragEndIndex)
                    {
                        int temporaryIndex = this.dragStartIndex;

                        this.dragStartIndex = this.dragEndIndex;

                        this.dragEndIndex = temporaryIndex;
                    }
                }

                this.surpressItemSelectionChanged = true;

                if(SingleSelect)
                {
                    SelectorItem startSelectorItem = ItemContainerGenerator.ContainerFromIndex(this.dragStartIndex) as SelectorItem;

                    startSelectorItem.IsSelected = !startSelectorItem.IsSelected;

                    if(startSelectorItem.IsSelected)
                    {
                        for(int i = 0; i < sourceList.Count; i++)
                        {
                            SelectorItem selectorItem = ItemContainerGenerator.ContainerFromIndex(i) as SelectorItem;

                            if(selectorItem == startSelectorItem)
                            {
                                continue;
                            }

                            selectorItem.IsSelected = false;
                        }
                    }
                }
                else
                {
                    for(int i = this.dragStartIndex; i < this.dragEndIndex + 1; i++)
                    {
                        SelectorItem selectorItem = ItemContainerGenerator.ContainerFromIndex(i) as SelectorItem;

                        selectorItem.IsSelected = !selectorItem.IsSelected;
                    }
                }

                this.surpressItemSelectionChanged = false;

                ItemSelectionChangedEventArgs itemSelectionChangedEventArgs = new ItemSelectionChangedEventArgs
                (
                    Selector.ItemSelectionChangedEvent,
                    this,
                    null,
                    true
                );

                RaiseEvent(itemSelectionChangedEventArgs);
            }
        }

        #endregion
    }
}
728x90
반응형
그리드형
Posted by 사용자 icodebroker
TAG , ,

댓글을 달아 주세요