MVVM의 ViewModel or Model 부분을 구현할 때, View에 속성의 변경 알림을 위해 INotifyPropertyChanged 인터페이스를 이용합니다.

 

PropertyChangedEventArgs 이벤트 Class는 속성의 이름을 인자로 받게되는데, 잘못된 속성명을 넘겨도 Exception을 던지지 않습니다.

 

아래의 코드의 VerifyPropertyName 메서드와 같이 속성의 접근자가 public인지 실제로 존재하는 속성명인지 체크해줍니다.

 

Conditional 특성을 추가해서 DEBUG 상수가 정의되었을 때만 메서드를 호출하기 때문에, 배포시 Release 모드에서는 동작을 하지 않기에

 

성능 저하는 걱정하지 않으셔도 됩니다.

 

    [Serializable]

    public abstract class ObservableObject : INotifyPropertyChanged

    {

        [field: NonSerialized]

        public event PropertyChangedEventHandler PropertyChanged = delegate { };

 

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)

        {

            PropertyChanged(this, e);

        }

 

        protected void RaisePropertyChanged(string propertyName)

        {

            VerifyPropertyName(propertyName);

            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));

        }

 

        /// <summary>

        /// 디버그 모드일때만 인자로 넘긴 프로퍼티 이름이 실제 존재하는지 검사합니다.

        /// </summary>

        [Conditional("DEBUG")]

        [DebuggerStepThrough]

        public void VerifyPropertyName(string propertyName)

        {

            //public, 실제 존재하는지 검사

            if (TypeDescriptor.GetProperties(this)[propertyName] == null)

            {

                throw new ArgumentException("public 아니거나 존재하지 않는 속성입니다 : " + propertyName);

            }

        }

    }

 

CollectionViewSource Class를 이용해 정렬하기

MVVM 패턴 적용 중에 ViewModel에 현재 View ListBox에 바인딩 되어있는 Collection개체의 현재 선택된 항목을 이동시키거나 원본 Collection 개체를 수정하지 않고 View에만 정렬시켜 보여주고 싶을 때에는 CollectionViewSource Class를 사용하면 유용합니다. (정렬, 그룹화, 필터링 제공) 아래의 두 기능을 구현한 프로젝트입니다.

1.     SelectedIndex를 바인딩하지 않고 CollectionViewSource를 이용해 선택항목 이동 기능

2.     속성을 기준으로한 정렬 기능

예제 프로젝트에는 GalaSoftGalaSoft.MvvmLight툴을 이용하였습니다.

완성된 프로그램입니다. 선택된 나이와 이름을 표시하고, 이전,다음 버튼은 선택한 Item을 변경합니다. 정렬은 나이 및 이름으로 오름차순 정렬합니다.

 

                                                                                  

 

 

우선 Model입니다. 간단하게 이름과 나이로 Person Class를 구성하였습니다.

 

public class Person

    {

        private int _age;

        /// <summary>

        /// 나이를 가져옵니다.

        /// </summary>

        public int Age

        {

            get { return _age; }

            private set { _age = value; }

        }

 

        private string _name;

        /// <summary>

        /// 이름을 가져옵니다.

        /// </summary>

        public string Name

        {

            get { return _name; }

            private set { _name = value; }

        }

 

        public Person(int age, string name)

        {

            this.Age = age;

            this.Name = name;

        }

    }

 

 

다음은 ViewModel 입니다. ICommand 인터페이스를 이용해 버튼들의 Command를 바인딩하였습니다. 세부 내용은 주석을 참고해주세요.

 

 public class MainViewModel : ViewModelBase

    {

        private CollectionViewSource _personCollection;

        /// <summary>

        /// Person CollectionViewSource

        /// </summary>

        public CollectionViewSource PersonCollection

        {

            get { return _personCollection; }

            set { _personCollection = value; }

        }

 

        private RelayCommand<bool> _cmdChangeSelectedItem;

        /// <summary>

        /// 현재 선택된 아이템을 변경합니다.

        /// </summary>

        public RelayCommand<bool> CmdChangeSelectedItem

        {

            get

            {

                if (_cmdChangeSelectedItem == null)

                    _cmdChangeSelectedItem = new RelayCommand<bool>(ChangeSelectedItem, CanChangeSelectedItem);

               

                       

                return _cmdChangeSelectedItem;

            }

        }

 

        private RelayCommand<string> _cmdSortName;

 

        /// <summary>

        /// 오름차순 정렬

        /// </summary>

        public RelayCommand<string> CmdSortAsc

        {

            get

            {

                if (_cmdSortName == null)

                    _cmdSortName = new RelayCommand<string>(SortAsc);

                       

                return _cmdSortName;

            }

        }

 

 

        /// <summary>

        /// Initializes a new instance of the MainViewModel class.

        /// </summary>

        public MainViewModel()

        {

            if (IsInDesignMode)

            {

                // Code runs in Blend --> create design time data.

            }

            else

            {

                // Code runs "for real"

            }

            this.PersonCollection = new CollectionViewSource();

 

            List<Person>  persons = new List<Person>()

            {

                new Person(13, "김가나"),

                new Person(12, "감가나"),

                new Person(25, "하이노"),

                new Person(55, "김동휴"),

                new Person(15, "감강찬"),

                new Person(14, "백설왕"),

                new Person(95, "배고파"),

                new Person(35, "이삼사"),

            };

            this.PersonCollection.Source = persons;

        }

 

        /// <summary>

        /// 현재 선택한 Item 변경합니다.

        /// </summary>

        /// <param name="isNext">true=다음, false=이전</param>

        private void ChangeSelectedItem(bool isNext)

        {

            if (isNext)

            {

                this.PersonCollection.View.MoveCurrentToNext();

            }

            else

            {

                this.PersonCollection.View.MoveCurrentToPrevious();

            }

        }

 

        /// <summary>

        /// 현재 선택한 Item 변경할수있는지 검사합니다.

        /// </summary>

        /// <param name="isNext"></param>

        /// <returns></returns>

        private bool CanChangeSelectedItem(bool isNext)

        {

            //현재 컬렉션의 마지막 Index 가져온다

            int viewLastIdx = this.PersonCollection.View.Cast<object>().Count() - 1;

 

            //다음으로 이동가능한지 체크

            if (isNext && this.PersonCollection.View.CurrentPosition == viewLastIdx)

            {

                return false;

            }

            else if (isNext == false && this.PersonCollection.View.CurrentPosition == 0)

            {

                //이전으로 이동가능한지 체크

                return false;

            }

 

            return true;

        }

 

        /// <summary>

        /// 오름차순으로 정렬합니다.

        /// </summary>

        /// <param name="proName">속성명</param>

        private void SortAsc(string proName)

        {

            this.PersonCollection.View.SortDescriptions.Clear();

            this.PersonCollection.View.SortDescriptions.Add(new System.ComponentModel.SortDescription(proName, System.ComponentModel.ListSortDirection.Ascending));

        }

        ////public override void Cleanup()

        ////{

        ////    // Clean up if needed

 

        ////    base.Cleanup();

        ////}

    }

 

 

 

마지막으로 View Xaml코드입니다.

ListBox의 ItemSource는 ViewModel의 PersonCollection 개체의 View속성에 바인딩하였습니다.

 

    

<Grid x:Name="LayoutRoot">

        <ListBox ItemsSource="{Binding PersonCollection.View}" Margin="0,0,0,119" SelectionMode="Single">

            <ListBox.ItemTemplate>

                <DataTemplate>

                    <StackPanel Orientation="Horizontal">

                        <TextBlock Text="{Binding Age}"/>

                        <TextBlock Text="{Binding Name}" Margin="50,0,0,0"/>

                    </StackPanel>

                </DataTemplate>

            </ListBox.ItemTemplate>

        </ListBox>

        <TextBlock DataContext="{Binding PersonCollection.View}" Height="23" HorizontalAlignment="Left" Margin="12,148,0,0"        Text="{Binding Age}" VerticalAlignment="Top" />

        <TextBlock DataContext="{Binding PersonCollection.View}"  Height="23" HorizontalAlignment="Left" Margin="91,148,0,0" Text="{Binding Name}" VerticalAlignment="Top" />

        <Button Command="{Binding CmdChangeSelectedItem}" CommandParameter="{StaticResource False}" Content="이전" Height="23" HorizontalAlignment="Left" Margin="12,177,0,0" VerticalAlignment="Top" Width="40"/>

        <Button Command="{Binding CmdChangeSelectedItem}" CommandParameter="{StaticResource True}" Content="다음" Height="23" HorizontalAlignment="Left" Margin="58,177,0,0" VerticalAlignment="Top" Width="40" />

        <Button Command="{Binding CmdSortAsc}" CommandParameter="Age"  Content="나이 정렬" Height="23" HorizontalAlignment="Left" Margin="14,221,0,0" VerticalAlignment="Top" Width="71" />

        <Button Command="{Binding CmdSortAsc}" CommandParameter="Name" Content="이름 정렬" Height="23" HorizontalAlignment="Left" Margin="91,221,0,0" VerticalAlignment="Top" Width="71" />

</Grid>

 

 

 

 

 

CmdChangeSelectedItem의 CommandParameter의 True, False는 다음과 같습니다.

<sys:Boolean x:Key="True">True</sys:Boolean>

       <sys:Boolean x:Key="False">False</sys:Boolean>

 

CmdSortAsc CommandParameter는 정렬할 속성명을 인자로 넘겨주고 있습니다.

 

 

압축한 데모 프로젝트입니다.

 AboutCollectionViewSource.zip

 

 

기존 Style에 Style을 추가하려면 Style 클래스의 BaseOn 속성을 이용하면 됩니다.

 

아래의 코드와 같이 BasedOn 속성을 이용하시면 기존 Style을 기반으로 Style을 추가 및 수정 할 수 있습니다.

 

<Style x:Key="BaseImageButtonStyle" TargetType="{x:Type Button}">

 

<Style/>

 

 ...........

 

<Style x:Key="ExtendsImageButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource BaseImageButtonStyle }">

 

<Style/> 

 

 

 

 

 

 

'.Net > WPF' 카테고리의 다른 글

[MVVM] CollectionViewSource Class를 이용해 정렬하기  (0) 2013.07.29
WPF Xaml에서 특수문자 사용  (0) 2013.07.11
WPF 윈도우 포커스(focus) 가지 않게 하기  (0) 2013.07.10
WPF WndProc  (0) 2013.07.10
Binding.UpdateSourceTrigger  (0) 2013.07.09

예를들어 가상키보드를 구현할 때 가상키보드에 포커스가 옮겨지면 안됩니다.

 

Win32의 SetWindowLong 함수를 사용하시면 포커스가 이동되지 않는 Window를 생성하실 수 있습니다.

 

 

 

private const int GWL_EXSTYLE = -20;

        private const int WS_EX_NOACTIVATE = 0x08000000;

 

        [DllImport("user32.dll")]

        public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

 

        [DllImport("user32.dll")]

        public static extern int GetWindowLong(IntPtr hWnd, int nIndex);

 

        protected override void OnSourceInitialized(EventArgs e)

        {

            base.OnSourceInitialized(e);

 

            WindowInteropHelper helper = new WindowInteropHelper(this);

            IntPtr ip = SetWindowLong(helper.Handle, GWL_EXSTYLE,

                GetWindowLong(helper.Handle, GWL_EXSTYLE) | WS_EX_NOACTIVATE);

        } 

 

 

 

 

 

 

'.Net > WPF' 카테고리의 다른 글

WPF Xaml에서 특수문자 사용  (0) 2013.07.11
WPF 기존 Style에 Style 추가  (0) 2013.07.11
WPF WndProc  (0) 2013.07.10
Binding.UpdateSourceTrigger  (0) 2013.07.09
[MVVM Galasoft.MvvmLight.WPF4] EventToCommand, 이벤트 정보를 넘기자!  (0) 2013.07.05

Winform Form Class에서는 윈도우 프로시져를 오버라이드 할 수 있게 제공해주지만

 

WPF Window Class에는 제공해주지 않고 있습니다.

 

아래의 코드와 같이 하시면 윈도우 프로시져를 확인 하실 수 있습니다.

 

 

 

        [DllImport("user32.dll")]

        static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);

        private const int WM_DRAWCLIPBOARD = 0x308;

        private const int WM_CHANGECBCHAIN = 0x30D;

 

        void OnLoaded(object sender, RoutedEventArgs e)

        {           

            HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);

            source.AddHook(new HwndSourceHook(WndProc));

 

 

            IntPtr mNextClipBoardViewerHWnd = SetClipboardViewer(new System.Windows.Interop.WindowInteropHelper(this).Handle);

        }

 

 

 

 

 

 

 

Binding 클래스의 UpdateSourceTrigger 속성은 바인딩 소스 업데이트 타이밍을 결정하는 값을 가져오거나 설정할때 사용되는 속성입니다.

 

Xaml에서는 아래와 같이 사용합니다.

 

 

 Text="{Binding Description,UpdateSourceTrigger=PropertyChanged}" 

 

 

 

UpdateSourceTrigger의 열거형식은 아래와 같습니다.

 

멤버 이름 

 설명

 Default

 바인딩 대상 속성의 기본 UpdateSourceTrigger 값입니다. Text속성의 기본값은 LostFocus이지만, 대부분의 종속성 속성의 기본값은 PropertyChanged입니다.

종속성 속성의 기본 UpdateSourceTrigger 값을 확인하는 프로그래밍 방식은 GetMetadata를 사용하여 속성의 속성 메타데이터를 가져온 다음 DefaultUpdateSourceTrigger 속성의 값을 확인하는 것입니다.

 PropertyChanged

 바인딩 대상 속성이 변경될 때마다 바인딩 소스를 즉시 업데이트합니다.

 LostFocus

 바인딩 대상 요소가 포커스를 잃을 때마다 바인딩 소스를 업데이트합니다.

 Explicit

 UpdateSource 메서드를 호출할 때만 바인딩 소스를 업데이트합니다.

예를 들들면 아래와 같은 코드입니다.


 BindingExpression be = textBox1.GetBindingExpression(TextBox.TextProperty);
 be.UpdateSource();

 

 

 [참조 : MSDN]

 

 

 

 시나리오 마다 다르겠지만, 대부분의 컨트롤을 사용할때에는 Default 값을 사용하면 문제가 보통 없습니다만 TextBox의 Text 속성에 바인딩할 때,

 

포커스를 잃을 때만 소스가 업데이트 되기 때문에 문제가 발생합니다.

 

 예를들어 TextBox에 사용자가 문자열을 입력한 후, 수정 버튼을 클릭하는 시나리오에서 수정 버튼은 Text 속성에 바인딩 되어 있는 소스의 값이 String.IsNullEmpty값이

 

true일때만 활성화시킨다면 문제가 발생합니다.

 

 위의 표의 설명과 같이 Text의 Default 값은 LostFocus(포커스를 잃을 때)이기 때문입니다.

 

 TextBox Text 속성에 바인딩 할때에 PropertyChanged 속성을 사용하면 문제가 해결합니다. 

 

 

단 MSDN에서는 이렇게 얘기합니다.

 

 키 입력이 있을 때마다 업데이트하면 성능이 떨어지고, 일반적으로 사용자가 새 값을 커밋하기 전에 입력 오류를 수정하고 백스페이스 키를 누를 수 있는 기회가 없어집니다.

[참조 : MSDN - Binding.UpdateSourceTrigger]

 

 

 

 

 

 

 

+ Recent posts