MVVM에서 연쇄적인 Combobox Contents 값 처리에 대한 질문입니다.

질문 제목을 영어로 적으면 찾기 힘들고, 한국어로 적다보니 말이 좀 이상하네요;;;

하기 구조로 된 JSON 형식을 parsing해서 List가 포함된 Object로 변환해서 XAML에서 2개의 ComboBox로 Depth2와 그에 따른 Depth3 항목을 보여주려고 합니다.

{
  "Depth1": [
    {
      "Depth2": ["Depth3_1"]
      "Depth2": [ "Depth3_1", "Depth3_2", "Depth3_3" ]
    }
 ]
}

현재 구현한 방식은 아래와 같습니다.

  • 첫번째 콤보박스에서 Depth2를 선택하면 codebehind를 통해서 combobox 의 ComboBox.DropDownClosed로 현재의 Depth2 값을 인지
  • 이를 delegate 함수를 통해서 VM까지 가져옴
  • Depth2에 해당하는 Depth3리스트를 Property에 담아서 XAML에서 보여줌.
<Border Grid.Row="1" Grid.Column="0">
     <TextBlock Text="Depth2" />
</Border>
<Border Grid.Row="1"Grid.Column="1">
  <ComboBox x:Name="ComboModel1"
    Width="150"
    Height="20"
    ItemsSource="{Binding JSONModel.Depth2}"
    SelectedItem="{Binding SelectedFiltering.Depth2}">
      <ComboBox.ItemTemplate>
        <DataTemplate>
          <StackPanel Orientation="Horizontal">
            <TextBlock   Text="{Binding ProductName}" />
          </StackPanel>
       </DataTemplate>
      </ComboBox.ItemTemplate>
     </ComboBox>
</Border>
<Border Grid.Row="1"Grid.Column="2">
  <TextBlock Text="Depth3" />
</Border>
<Border Grid.Row="1"Grid.Column="3">
  <ComboBox x:Name="ComboModel4"
     Width="150"
     Height="20"
     ItemsSource="{Binding Depth3}" SelectedItem="{Binding SelectedFiltering.Depth3}">
      <ComboBox.ItemTemplate>
        <DataTemplate>
           <StackPanel Orientation="Horizontal">
              <TextBlock   Text="{Binding }" />
           </StackPanel>
       </DataTemplate>
     </ComboBox.ItemTemplate>
  </ComboBox>
</Border>

하지만 이보다 효율적으로 MVVM에서 순차적으로 선택된 값을 보여주는 콤보박스를 처리하는 방법 있을까요?

1개의 좋아요

제가 생각을 너무 많이 했나봅니다… 그냥 SelectedItem을 사용해도 되는데… ㅎㅎ

3개의 좋아요

앞서 작성한 XAML코드에서 오류가 발생한 부분이 SelectedItem="{Binding SelectedFiltering.Depth2}"> 영역이었습니다.

Combobox에서 선택된 값을 VM에서 string타입으로 선언한 Property에선 값을 잘 받아오는데, 이게 object 타입으로 선언한 SelectedFiltering의 특정 Property로 지정해서 가져오려고 하면 trigger가 안되는 부분이 있습니다.

Combobox의 SelectItem을 아래와 같이 수정하고 string으로 선언한 경우 → set으로 넘어옴.

SelectedItem="{Binding GetDepth2}">
private string _getDepth2;
        public string GetDepth2
        {
            get => _getDepth2;
            set
            {
                _getDepth2= value;
                OnPropertyChanged(nameof(ProductName1));

            }
        }

object 타입으로 선언한 SelectedFiltering의 하나의 Property → set으로 넘어오지 못함.

SelectedItem="{Binding SelectedFiltering.Depth2}">
private UserFiltering _selectedFiltering;
        public UserFiltering SelectedFiltering
        {
            get => _selectedFiltering;
            set
            {
                Softwares.Clear();
                _selectedFiltering = value;
                OnPropertyChanged(nameof(SelectedFiltering));

            }
        }
public class UserFiltering
    {
        public string Depth1 { get; set; }
        public string Depth2 { get; set; }
        public string Depth3 { get; set; }
    }

이처럼 SelectedItem에서 Object 타입의 특정 프로퍼티로 바로 할당 받는 방법이 있을까요?

1개의 좋아요

object (또는 정확한 타입)으로 SelectedItem에 바인딩하려면 ItemsSource의 바인딩된 목록의 노드 유형과 데이터유형이 같아야 합니다. 그게 SelectedItem의 의미니까요. 아니면, SelectedValueSelectedValuePath을 이용해 특정 노드의 특정 값을 선택할 수 있습니다.

2개의 좋아요

말씀해주신대로 SelectedValueSelectedValuePath를 사룡하려고 하다보니 제대로 적용이 되지 않는 부분이 있어서 보다 자세한 코드와 함께 문의드립니다.

기존에 하기 구조로 된 JSON이 있습니다.

{
  "Depth1_1": [ "-- --", "Value1", "Value2" ],
  "Depth1_2": [ "-- --", "Value1", "Value2" ],
  "Depth1_3": [
    {
      "Depth2_1": "Value1",
      "Depth2_2": [ "-- --" ]
    },
    {
      "Depth2_1": "Value1",
      "Depth2_2": [ "-- --", "Value1", "Value2", "Value3", "Value4" ]
    },
 ]
}

이를 아래와 같이 class로 전환시켰습니다. VM에서 Combobox에서 사용될 Itemsource로써의 BaseFilteringInfo와 사용자의 Combobox 선택값에 따라 반환될 SelectedFiltering은 아래와 같습니다.

    public class BaseFiltering
    {
        public IList<string> Depth1_1 { get; set; }
        public IList<string> Depth1_2 { get; set; }
        public IList<Depth2> Depth1_3 { get; set; }
    }
    public class Depth2
    {
        public string Depth2_1 { get; set; }
        public IList<string> Depth2_2 { get; set; }
    }

특히 UserFiltering은 사용자가 지정한 값만을 할당하기 위해서 string 값만을 데이터 타입으로 지정하였습니다.

public class UserFiltering
    {
        public string Depth1_1 { get; set; }
        public string Depth1_2 { get; set; }
        public string Depth2_1 { get; set; }
        public string Depth2_2 { get; set; }
    }
private BaseFiltering _baseFilteringInfo;
        public BaseFiltering BaseFilteringInfo
        {
            get { return _baseFilteringInfo; }
            set
            {
                _baseFilteringInfo = value;
                OnPropertyChanged(nameof(SelectedTabIndex));
            }
        }

private UserFiltering _selectedFiltering;
        public UserFiltering SelectedFiltering
        {
            get => _selectedFiltering;
            set
            {               
                _selectedFiltering = value;
                OnPropertyChanged(nameof(SelectedFiltering));
                
            }
        }

우선, 문제가 발생한 부분은 크게 2가지 입니다.

  • 하기 Combobox에서는 BaseFilteringInfo.Depth1_1에 대한 Items을 보여주지만, 사용자가 선택할 때 SelectedValuePath에 대한 표현이 정확하지 않아 값을 못 가져오는 경우 입니다.
<ComboBox x:Name="ComboModel1"
  ItemsSource="{Binding BaseFilteringInfo.Depth1_1}"
  SelectedValue="{Binding SelectedFiltering.Depth1_1, Mode=TwoWay}"
  SelectedValuePath="Depth1_1" />
  • 다음은 DisplayMemberPath를 통해서 Depth2_1에 대한 items을 정상으로 콤보박스에서 출력되는 것을 확인되어 SelectedValuePath는 정확한 것 같지만, 이역시 SelectedValue를 통해서 가져오지 못하고 있습니다.
<ComboBox x:Name="ComboModel3" ItemsSource="{Binding BaseFilteringInfo.Depth2_1}"
   DisplayMemberPath="Depth2_1"
   SelectedValue="{Binding SelectedFiltering.Depth2_1, Mode=TwoWay}"
   SelectedValuePath="Depth2_1"/>

두가지 모두 SelectedValue를 통해서 SelectedFiltering으로 우선 넘어오지 못하는 것으로 확인되었습니다. 제가 잘못 적용한 부분이 있다면 조언을 부탁드립니다.

읽어주셔서 감사합니다.

1개의 좋아요

제가 원하는 정확한 동작을 파악하기 어려워 이해한 내용으로 간단히 구현을 해봤습니다. 실행해 보고 동작이 원하는 결과에 근접하다면 활용하면 되겠습니다.

  • MVVM 요소는 없습니다. 다만 Depth1.Text 또는 Depth2.Text가 변경 표시되도록 INotifyPropertyChanged는 구현하였습니다.

  • 필터 목록을 담는 클래스를 좀 더 쓰기편하도록 구현하였습니다. 하이라키 구조이며 구조는 다음과 같습니다.

    public class FilterInfo
    {
        public string Text { get; }
        public IEnumerable<FilterInfo> Children { get; }
        
        public FilterInfo(string text, IEnumerable<FilterInfo> children)
        {
            this.Text = text;
            this.Children = children;
        }
    }
  • ComboBox 바인딩은 다음과 같습니다.
...
        <TextBlock
            Grid.Row="0"
            Grid.Column="0"
            Text="Filter1" />
        <ComboBox
            Grid.Row="0"
            Grid.Column="1"
            DisplayMemberPath="Text"
            ItemsSource="{Binding RootFilter.Children}"
            SelectedItem="{Binding SelectedFilter.Depth1, Mode=TwoWay}" />
        <TextBlock Grid.Column="2" Text="Filter2" />
        <ComboBox
            Grid.Row="0"
            Grid.Column="3"
            DisplayMemberPath="Text"
            ItemsSource="{Binding SelectedFilter.Depth1.Children}"
            SelectedItem="{Binding SelectedFilter.Depth2, Mode=TwoWay}" />

        <TextBlock
            Grid.Row="1"
            Grid.Column="0"
            Text="Selected Depth1" />
        <TextBlock
            Grid.Row="1"
            Grid.Column="1"
            Text="{Binding SelectedFilter.Depth1.Text, Mode=OneWay}" />
        <TextBlock
            Grid.Row="1"
            Grid.Column="2"
            Text="Selected Depth2" />
        <TextBlock
            Grid.Row="1"
            Grid.Column="3"
            Text="{Binding SelectedFilter.Depth2.Text, Mode=OneWay}" />
...

image

소스코드

2개의 좋아요

@dimohy 자료 정말 감사합니다. 조언 주신대로 적용해보고 결과를 공유드리겠습니다. 좋은 주말 되세요!

2개의 좋아요

@dimohy 우선, 제가 예시로 들었던 JSON 파일이 잘못되었습니다. 급하게 예시를 만들려고 하다보니… 이 부분이 아무래도 혼란스럽게 만들어 드린 것 같아서 죄송합니다. *각 Depth마다 Key값이 상이한 구조입니다.

 "Depth1_1_Key": [
    {
      "Depth2_1_1_Key": "-- --",
      "Depth2_1_2_Key": [ { "Depth3_1_1": "-- --" } ]
    },
    {
      "Depth2_2_1_Key": "Value",
      "Depth2_2_2_Key": [
        { "Depth3_2_1_Key": "-- --" },
        { "Depth3_2_2_Key": "Value" },
        { "Depth3_2_3_Key": "Value" },
      ]
    },
    {
      "Depth2_3_1_Key": "Value",
      "Depth2_3_2_Key": [
        { "Depth3_3_1_Key": "-- --" }
      ]
    },
...

또한 SelectedItem 이 아닌, SelectedValueSelectedValuePath을 적용하였습니다. SelectedItem을 적용할까 싶었지만, IEnumerable로 구성된 RootFilter과 일반 string 타입(값이 할당 받을 데이터 타입)만 가진 SelectedFiltering과의 노드 유형이 달라서 적용하기 제한적이었습니다.

또한 자료를 찾아보니, SelectedValueSelectedValuePathstring 타입으로 반환이 되는 방식이라서 처음 제가 의도했던 것처럼, SelectedValuePathSelectedFiltering.Depth2_1 같이 객체의 프로퍼티로 바로 할당할 수 없었습니다.

그래서 현재 적용해놓은 방식은 SelectedValueSelectedValuePath을 통해서 VM에서 OnPropertyChanged로 지정한 string타입의 프로터티로 할당을 받고, 이를 BaseFiltering 객체에서 LINQ로 하위 List정보를 다시 Second Combox의 ItemsSource로 지정하였습니다.

할당받은 string을 추후 event trigger를 통해서 SelectedFiltering 객체에 재할당하는 방식으로 적용하였는데… 뭔가… 아쉬움이 많이 남습니다…

2개의 좋아요