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개의 좋아요

이 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개의 좋아요

MainWindow의 DataContext

Test의 DataContext

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

2개의 좋아요

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

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

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

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

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

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

1개의 좋아요

현재 데브익스프레스 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개의 좋아요

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

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

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

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

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

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

1개의 좋아요

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

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

기존 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개의 좋아요

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

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

1개의 좋아요