DevExpress GridControl View(CardView, TableView) 런타임 변경

<Window x:Class="GridExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
        xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:GridExample"
        Width="500" Height="370">

    <Window.DataContext>
        <local:PersonsViewModel />
    </Window.DataContext>
    
    <Window.Resources>
        <dxg:TableView x:Key="TableView"/>
        <dxg:CardView x:Key="CardView"/>
        <dxg:TreeListView x:Key="TreeListView"/>
        
        <Style TargetType="dxg:GridControl">
            <Style.Triggers>
                <DataTrigger Binding="{Binding CurrentView}">
                    <DataTrigger.Value>
                        <x:StaticExtension Member="local:GridViews.TableView"/>
                    </DataTrigger.Value>
                    <Setter Property="View" Value="{StaticResource TableView}"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding CurrentView}">
                    <DataTrigger.Value>
                        <x:StaticExtension Member="local:GridViews.CardView"/>
                    </DataTrigger.Value>
                    <Setter Property="View" Value="{StaticResource CardView}"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding CurrentView}">
                    <DataTrigger.Value>
                        <x:StaticExtension Member="local:GridViews.TreeView"/>
                    </DataTrigger.Value>
                    <Setter Property="View" Value="{StaticResource TreeListView}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <StackPanel>
        <dxe:ComboBoxEdit SelectedItem="{Binding CurrentView, Mode=TwoWay}"
                          IsTextEditable="False"
                          ItemsSource="{Binding Views}"
                          Width="150" Height="24" >
        </dxe:ComboBoxEdit>
        <dxg:GridControl Name="grid" Height="300"
                         ItemsSource="{Binding Persons}"
                         AutoPopulateColumns="True" >
        </dxg:GridControl>
    </StackPanel>
</Window>

Window에서는 DevExpress GridControl View(CardView, TableView) 런타임 시 변경이 가능합니다.
해당 코드가 UserControl에서는 동작하지않는데 이유를 아시는 고수님이 계실까요?

1 Like

이 XAML의 Window를 그대로 UserControl로 바꾸신거라면 그 UserControl로 바꾸신 XAML도 써주셔야 할 거 같습니다.

소스코드를 다 올리기 어려우시면 일부만 Github Gist를 이용하시는 방법도 있습니다.

MainWindow.xaml

<Window x:Class="GridExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:GridExample"
        Width="500" Height="370">

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

    <Grid>
        <local:Test/>
    </Grid>
</Window>

Test.xaml

<UserControl x:Class="GridExample.Test"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
             xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:local="clr-namespace:GridExample"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <dxg:TableView x:Key="TableView"/>
        <dxg:CardView x:Key="CardView"/>
        <dxg:TreeListView x:Key="TreeListView"/>
        
        <Style TargetType="dxg:GridControl">
            <Style.Triggers>
                <DataTrigger Binding="{Binding CurrentView}">
                    <DataTrigger.Value>
                        <x:StaticExtension Member="local:GridViews.TableView"/>
                    </DataTrigger.Value>
                    <Setter Property="View" Value="{StaticResource TableView}"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding CurrentView}">
                    <DataTrigger.Value>
                        <x:StaticExtension Member="local:GridViews.CardView"/>
                    </DataTrigger.Value>
                    <Setter Property="View" Value="{StaticResource CardView}"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding CurrentView}">
                    <DataTrigger.Value>
                        <x:StaticExtension Member="local:GridViews.TreeView"/>
                    </DataTrigger.Value>
                    <Setter Property="View" Value="{StaticResource TreeListView}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </UserControl.Resources>

    <StackPanel>
        <dxe:ComboBoxEdit SelectedItem="{Binding CurrentView, Mode=TwoWay}"
                          IsTextEditable="False"
                          ItemsSource="{Binding Views}"
                          Width="150" Height="24" >
        </dxe:ComboBoxEdit>
        <dxg:GridControl Name="grid" Height="300"
                         DataSource="{Binding Persons}"
                         AutoPopulateColumns="True" >
        </dxg:GridControl>
    </StackPanel>
</UserControl>

간단하게 예를들면 위와 같이 구성되어 있습니다.

이 부분 때문에 바인딩이 걸리지 않아서 동작하지 않는 것으로 보입니다.

바인딩 자체가 걸리지 않았기 때문에 바인딩 에러 또한 발생하지 않은 것으로 보이네요.

snoopwpf/snoopwpf: Snoop - The WPF Spy Utility (github.com)

위 프로그램을 사용하셔서 UserControl의 DataContext에 뷰모델의 특정 프로퍼티와 연결하시는 것을 확인해보시기 바랍니다.

1 Like

MainWindow의 DataContext

Test의 DataContext

제가 확인했을 때 정상적으로 바인딩이 되어있습니다.
ObservableCollection인 Persons이 바인딩되어 데이터 또한 잘 표시됩니다.
콤보박스로 CurrentView를 변경했을 때에도 값이 제대로 변하고있습니다.
하지만, CardView로 변경이 되지 않고있습니다.

2 Likes

wpf의 자동 데이터 컨텍스트 상속기능이 있었네요 참.

저도 말하고 이제 생각이 나다니…

아무래도 직접 실행해보기전까지는 파악이 어려울 것 같은데, 오늘 저녁에나 테스트를 해볼 수 있을 것 같습니다.

그전에 해결되시면 좋겠지만, 그게 아니라면 밤에 위 소스를 가지고 테스트해보겠습니다.

체험판 버전을 사용해야하는데 현재 데브익스프레스 버전은 최신버전 사용하시는 걸까요?

위의 소스만 있으면 현상을 그대로 재현할 수 있을까요?

1 Like

현재 데브익스프레스 18.2 버전을 사용중이지만 최신버전을 사용해도 상관없을거같습니다.
참고하실 수 있는 코드를 올려놓겠습니다.

MainWIndow.xaml

<Window x:Class="GridExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:GridExample"
        xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
        xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
        Width="500" Height="370">

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

    <!--<Window.Resources>
        <dxg:TableView x:Key="TableView"/>
        <dxg:CardView x:Key="CardView"/>
        <dxg:TreeListView x:Key="TreeListView"/>

        <Style TargetType="dxg:GridControl">
            <Style.Triggers>
                <DataTrigger Binding="{Binding CurrentView}">
                    <DataTrigger.Value>
                        <x:StaticExtension Member="local:GridViews.TableView"/>
                    </DataTrigger.Value>
                    <Setter Property="View" Value="{StaticResource TableView}"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding CurrentView}">
                    <DataTrigger.Value>
                        <x:StaticExtension Member="local:GridViews.CardView"/>
                    </DataTrigger.Value>
                    <Setter Property="View" Value="{StaticResource CardView}"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding CurrentView}">
                    <DataTrigger.Value>
                        <x:StaticExtension Member="local:GridViews.TreeView"/>
                    </DataTrigger.Value>
                    <Setter Property="View" Value="{StaticResource TreeListView}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>-->

    <StackPanel>
        <!--<dxe:ComboBoxEdit SelectedItem="{Binding CurrentView, Mode=TwoWay}"
                          IsTextEditable="False"
                          ItemsSource="{Binding Views}"
                          Width="150" Height="24" >
        </dxe:ComboBoxEdit>
        <dxg:GridControl Name="grid" Height="300"
                         DataSource="{Binding Persons}"
                         AutoPopulateColumns="True" >
        </dxg:GridControl>-->
        <local:Test/>
    </StackPanel>
</Window>

Test.xaml

<UserControl x:Class="GridExample.Test"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
             xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:local="clr-namespace:GridExample"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <dxg:TableView x:Key="TableView"/>
        <dxg:CardView x:Key="CardView"/>
        <dxg:TreeListView x:Key="TreeListView"/>
        
        <Style TargetType="dxg:GridControl">
            <Style.Triggers>
                <DataTrigger Binding="{Binding CurrentView}">
                    <DataTrigger.Value>
                        <x:StaticExtension Member="local:GridViews.TableView"/>
                    </DataTrigger.Value>
                    <Setter Property="View" Value="{StaticResource TableView}"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding CurrentView}">
                    <DataTrigger.Value>
                        <x:StaticExtension Member="local:GridViews.CardView"/>
                    </DataTrigger.Value>
                    <Setter Property="View" Value="{StaticResource CardView}"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding CurrentView}">
                    <DataTrigger.Value>
                        <x:StaticExtension Member="local:GridViews.TreeView"/>
                    </DataTrigger.Value>
                    <Setter Property="View" Value="{StaticResource TreeListView}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </UserControl.Resources>

    <StackPanel>
        <dxe:ComboBoxEdit SelectedItem="{Binding CurrentView, Mode=TwoWay}"
                          IsTextEditable="False"
                          ItemsSource="{Binding Views}"
                          Width="150" Height="24" >
        </dxe:ComboBoxEdit>
        <dxg:GridControl Name="grid" Height="300"
                         DataSource="{Binding Persons}"
                         AutoPopulateColumns="True" >
        </dxg:GridControl>
    </StackPanel>
</UserControl>

PersonDataSource.cs

using DevExpress.Mvvm;
using DevExpress.Mvvm.DataAnnotations;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace GridExample
{

    public enum GridViews { TableView, CardView, TreeView };

    public class PersonsViewModel: ViewModelBase
    {
        ObservableCollection<Person> persons;
        
        public PersonsViewModel() {
            List<string> Names = new List<string> { "Alex", "Alice", "Tony", "Den", "Andrew", "John", "Donald", "Brian", "Effy", "Lisa", "Matthew" };
            persons = new ObservableCollection<Person>();
            for (int i = 0; i < 10; i++) {
                persons.Add(new Person(Names[i], "Last name " + i, 21 + i, i % 2 == 0, 170 + i, 75 + i));
            }
            persons[5].Age = 22;
            persons[8].Age = 50;
        }

        public List<GridViews> Views {
            get {
                return new List<GridViews>() { GridViews.TableView, GridViews.CardView, GridViews.TreeView };
            }
        }

        GridViews view = GridViews.TableView;
        public GridViews CurrentView {
            get {
                return view;
            }
            set {
                view = value;
                RaisePropertyChanged("CurrentView");
            }
        }

        public ObservableCollection<Person> Persons {
            get {
                return persons;
            }
        }

        [Command]
        public void Card()
        {
            CurrentView = GridViews.CardView;
        }

        [Command]
        public void Table()
        {
            CurrentView = GridViews.TableView;
        }
    }

    public class Person : INotifyPropertyChanged, IDataErrorInfo {
        const string lastNamePropertyName = "LastName";
        const string firstNamePropertyName = "FirstName";
        const string agePropertyName = "Age";
        const string heightPropertyName = "IsMarried";
        const string isMarriedPropertyName = "Height";
        const string weightPtropertyName = "Weight";

        string firstName;
        string lastName;
        int age;
        bool isMarried;
        int height;
        int weight;
        PersonPropertiesValidator Validator = new PersonPropertiesValidator();

        public Person(string firstName, string lastName, int age, bool isMarried, int height, int weight) {
            FirstName = firstName;
            LastName = lastName;
            Age = age;
            IsMarried = isMarried;
            Weight = weight;
            Height = height;
        }

        public string FirstName {
            get {
                return firstName;
            }
            set {
                Validator.IsNameValid(value, firstNamePropertyName);
                firstName = value;
                RaisePropertyChanged(firstNamePropertyName);
            }
        }

        public string LastName {
            get {
                return lastName;
            }
            set {
                Validator.IsNameValid(value, lastNamePropertyName);
                lastName = value;
                RaisePropertyChanged(lastNamePropertyName);
            }
        }

        public int Age {
            get {
                return age;
            }
            set {
                Validator.IsAgeValid(value, agePropertyName);
                age = value;
                RaisePropertyChanged(agePropertyName);
            }
        }

        public bool IsMarried {
            get {
                return isMarried;
            }
            set {
                isMarried = value;
                RaisePropertyChanged(isMarriedPropertyName);
            }
        }

        public int Height {
            get {
                return height;
            }
            set {
                height = value;
                RaisePropertyChanged(heightPropertyName);
            }
        }

        public int Weight {
            get {
                return weight;
            }
            set {
                weight = value;
                RaisePropertyChanged(weightPtropertyName);
            }
        }

        #region INotifyPropertyChanged members
        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName) {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        #region IDataErrorInfo members
        public string Error {
            get {
                return Validator.DataError;
            }
        }

        public string this[string columnName] {
            get {
                if (Validator.DataErrors.ContainsKey(columnName))
                    return Validator.DataErrors[columnName];
                else
                    return null;
            }
        }
        #endregion
    }

    public class PersonPropertiesValidator {
        string dataError = "";
        Dictionary<string, string> dataErrors = new Dictionary<string, string>();

        public bool IsNameValid(string value, string propertyName) {
            if (string.IsNullOrEmpty(value)) {
                dataErrors[propertyName] = "Full name is required.";
                return false;
            } else {
                ClearPropertyErrors(propertyName);
                return true;
            }
        }

        public bool IsAgeValid(int value, string propertyName) {
            if (value <= 0) {
                dataErrors[propertyName] = "Age validation failed.";
                return false;
            } else {
                ClearPropertyErrors(propertyName);
                return true;
            }
        }

        public string DataError {
            get {
                return dataError;
            }
        }

        public Dictionary<string, string> DataErrors {
            get {
                return dataErrors;
            }
        }

        void ClearPropertyErrors(string propertyName) {
            if (dataErrors.ContainsKey(propertyName))
                dataErrors.Remove(propertyName);
        }
    }
}

2 Likes

좀 살펴보느라 답이 늦었습니다.

연휴 간 좀 살펴봤는데, 저도 왜 변하지 않는지 굉장히 의심스럽습니다.

저도 시야가 순간 좁아져서 오류를 못발견하고 있는건지…저도 아는거도 막 헷갈려졌네요.

최신버전인 23버전이 아닌 18버전을 사용하시는 것으로 보아, 라이센스가 만료된 것을 갱신없이 사용하시는 것 같은데, 혹시라도 유료 라이센스가 살아 있으시다면 DevExpress 커뮤니티에 Ticket으로 질문을 올리셔서 도움을 받아 보실 수도 있을 듯 합니다.

저도 이유가 정말 궁금하네요…;

도움이 못 되어드려 죄송합니다.

1 Like

연휴에도 저의 고민을 위해 시간을 할애해주셔서 감사합니다.

저도 해당 문제를 해결하기 위해 다양한 시도를 한 결과 하나의 해결법을 찾긴했습니다.

기존 DataTrigger 방식이 아닌 Converter를 이용한 방식으로 해결할 수 있었습니다.

GridViewConver.cs

using System;
using System.Windows.Data;
using DevExpress.Xpf.Grid;

namespace GridExample
{
    class GridViewConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if ((GridViews)value == GridViews.CardView) return new CardView();
            if ((GridViews)value == GridViews.TreeView) return new TreeListView();
            return new TableView();
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Test.xaml

<UserControl x:Class="GridExample.Test"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
             xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:local="clr-namespace:GridExample"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <local:GridViewConverter x:Key="GridViewConverter"/>
    </UserControl.Resources>

    <StackPanel>
        <dxe:ComboBoxEdit SelectedItem="{Binding CurrentView, Mode=TwoWay}"
                          IsTextEditable="False"
                          ItemsSource="{Binding Views}"
                          Width="150" Height="24" >
        </dxe:ComboBoxEdit>
        <dxg:GridControl Name="grid" Height="300"
                         DataSource="{Binding Persons}"
                         AutoPopulateColumns="True"
                         View="{Binding CurrentView, Converter={StaticResource GridViewConverter}}">
        </dxg:GridControl>
    </StackPanel>
</UserControl>

3 Likes

해결하신 것은 정말 다행이네요.

하지만 …왜 위 코드가 동작하지 않는지는 의문이군요… 혹시라도 왜 위 코드가 동작하지 않는지 나중에 알게 되시면 기록 부탁드립니다.

1 Like