2016-07-24 17 views
1

Ok,in ComboBox mit dynamisch MVVM Rahmen

Ich habe ein paar ähnlichen Fragen gesehen, aber nicht in der Lage, dieses Problem in den letzten paar Tage, um herauszufinden. Ich habe zwei Comboboxen und ich möchte, dass jeder das ausgewählte Element in dem anderen versteckt. Wenn ich beispielsweise einen Wert in ComboBox 1 auswähle, sollte das ausgewählte Element als Option in ComboBox 2 entfernt werden.

Ich dachte über die Verwendung eines Befehls nach, aber ComboBoxes haben keine Befehle. Ich habe unter den XAML- und ViewModel-Code der Comboboxen eingefügt. Ich würde jede Hilfe mit diesem schätzen. Ich weiß, dass der Code unten falsch ist, aber ich denke, dass die Logik dafür in den Setter der ItemSource beschränkt sein sollte.

   <ComboBox Margin="0,7,0,0" 
          Name="ComboBoxA" 
          HorizontalAlignment="Stretch" 
          Header="{Binding AccountHeader}" 
          ItemTemplate="{StaticResource ComboBoxTemplate}" 
          ItemsSource="{Binding ChargedAccounts, 
               Mode=TwoWay, 
               UpdateSourceTrigger=PropertyChanged}" 

          SelectedItem="{Binding SelectedAccount, 
               Mode=TwoWay, 
               UpdateSourceTrigger=PropertyChanged}" /> 

       <ComboBox x:Uid="TargetAccountTextBox" 
          Name="ComboBoxB" 
          Margin="0,7,0,0" 
          HorizontalAlignment="Stretch" 
          Header="target account" 
          ItemTemplate="{StaticResource ComboBoxTemplate}" 
          ItemsSource="{Binding TargetAccounts, 
               Mode=TwoWay, 
namespace MoneyFox.Shared.ViewModels 
{ 
    [ImplementPropertyChanged] 
    public class ModifyPaymentViewModel : BaseViewModel, IDisposable 
    { 
     private readonly IDefaultManager defaultManager; 
     private readonly IDialogService dialogService; 
    private readonly IPaymentManager paymentManager; 

    //this token ensures that we will be notified when a message is sent. 
    private readonly MvxSubscriptionToken token; 
    private readonly IUnitOfWork unitOfWork; 

    // This has to be static in order to keep the value even if you leave the page to select a category. 
    private double amount; 
    private Payment selectedPayment; 

    public ModifyPaymentViewModel(IUnitOfWork unitOfWork, 
     IDialogService dialogService, 
     IPaymentManager paymentManager, 
     IDefaultManager defaultManager) 
    { 
     this.unitOfWork = unitOfWork; 
     this.dialogService = dialogService; 
     this.paymentManager = paymentManager; 
     this.defaultManager = defaultManager; 

     TargetAccounts = unitOfWork.AccountRepository.Data; 
     ChargedAccounts = unitOfWork.AccountRepository.Data; 
     token = MessageHub.Subscribe<CategorySelectedMessage>(ReceiveMessage); 
    } 


    ObservableCollection<Account> _SelectedAccount; 
    ObservableCollection<Account> SelectedAccount 
    { 

     get 
     { 
      return _SelectedAccount; 
     } 
     set 
     { 

      _SelectedAccount = value; 
      for(int i = 0; i < ChargedAccounts.Count; i++) 
      { 
       if(ChargedAccounts[i].ToString() == _SelectedAccount.ToString()) 
       { 
        ChargedAccounts.Remove(ChargedAccounts[i]); 
       } 
      } 

     } 

    } 

    ObservableCollection<Account> _TargetAccount; 
    ObservableCollection<Account> Targetccount 
    { 

     get 
     { 
      return _SelectedAccount; 
     } 
     set 
     { 

      _SelectedAccount = value; 
      for (int i = 0; i < TargetAccounts.Count; i++) 
      { 
       if (TargetAccounts[i].ToString() == _SelectedAccount.ToString()) 
       { 
        TargetAccounts.Remove(ChargedAccounts[i]); 
       } 
      } 

     } 

    } 

Antwort

-1

Gib den Comboboxen eine ItemContainerStyle (TargetType="ComboBoxItem") mit einem Daten Trigger. Für ComboBoxA, das wird wie folgt aussehen:

<ComboBox 
    ... 
    x:Name="ComboBoxA" 
    ... 
    > 
    <ComboBox.ItemContainerStyle> 
     <Style TargetType="ComboBoxItem"> 
      <Style.Triggers> 
       <DataTrigger Value="True"> 
        <DataTrigger.Binding> 
         <MultiBinding 
          Converter="{local:ObjectEquals}" 
          > 
          <Binding 
           Path="SelectedItem" 
           ElementName="ComboBoxB" /> 
          <!-- Binding with no properties just binds to the DataContext --> 
          <Binding /> 
         </MultiBinding> 
        </DataTrigger.Binding> 
        <Setter 
         Property="Visibility" 
         Value="Collapsed" /> 
       </DataTrigger> 
      </Style.Triggers> 
     </Style> 
    </ComboBox.ItemContainerStyle> 
</ComboBox> 

ComboBoxB bekommt die gleiche Sache, aber ElementName="ComboBoxA" in der SelectedItem Bindung.

Und wir müssen diesen mehrwertigen Konverter schreiben. Es ist so einfach, wie sie kommen:

public class ObjectEquals : MarkupExtension, IMultiValueConverter 
{ 
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 
    { 
     return values.Length == 2 && values[0] == values[1]; 
    } 

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 
    { 
     throw new NotImplementedException(); 
    } 

    public override object ProvideValue(IServiceProvider serviceProvider) 
    { 
     return this; 
    } 
} 

Es sei so praktisch, wenn man DataTrigger.Value zu {Binding} binden konnte, aber es ist keine Abhängigkeitseigenschaft.

Sie * könnte^dies auch rein im Ansichtsmodell von vorübergehend SelectedAccount von TargetAccounts entfernen - Sie eine private Voll _targetAccountsFull Liste haben würde, und ein öffentliches gefiltert. Der Setter für SelectedAccount würde die Liste filtern. Hast du das schon versucht?

Aber das ist nicht meine Idee einer guten Lösung. Das Ausblenden von Kombinationsfeldelementen ist UI-Design-Zeug; das Viewmodel sollte nicht mit einbezogen werden und sollte sich nicht einmal bewusst sein, dass solche Dinge stattfinden. Eine der Freuden von WPF/MVVM ist, dass Sie diese Dinge in der Ansicht in reinen UI-Code trennen können. Das Viewmodel hat seine eigene Komplexität, um die es sich zu kümmern gilt.

By the way, binden Sie SelectedItem zu SelectedAccount, aber SelectedAccount ist ein ObservableCollection. Das macht keinen Sinn. Es gibt ein ausgewähltes Konto. Machen Sie es zu einer einzigen Account, nicht eine Sammlung von ihnen.

0

Während ich mit vielen der Punkte in der Antwort von Ed einverstanden bin, gibt es einen einfacheren Weg, dies ohne DataTriggers oder Konverter zu tun.Es gibt bereits eine filtrierbaren CollectionViewSource im Rahmen, dass Ihr Freund ist (Scott Hanselman loves it)

Ich würde binden ComboBoxA zu Ihrer normalen ChargedAccounts Eigenschaft, aber ich würde ändern ComboBoxB zu:

  • bindet an eine Immobilie in der Code hinter der Ansicht, dass ein ICollectionView
  • in einem Selectionereignishandler für ComboBoxA (auch in dem Code hinter der Ansicht) zurückkehrt würde ich die Filter für den ICollectionView einstellt das aktuell ausgewählte Element
01 auszuschließen,

public ICollectionView FilteredData { get; set; } 

private void ComboBoxA_OnSelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    var z = new CollectionViewSource {Source = ViewModel.ChargedAccounts.Where(p => p != ViewModel.SelectedAccount) }; 
    FilteredData = z.View; 
} 

Natürlich ist dies vorausgesetzt, dass Sie das Richtige getan zu haben, indem vorzugsweise hinter Ihrer Ansicht als eine exponierte im Code ein Ansichtsmodell Eigenschaft:

Grob gesagt, dies kann in nur ein paar Zeilen durchgeführt werden Schnittstelle, und dass die Eigenschaften ChargedAccounts und SelectedAccount über diese Schnittstelle verfügbar sind.
Sie könnten auch diese paar Zeilen zusammen in Ihrem ViewModel cobble und triggern es über eine Änderung der Eigenschaft SelectedAccount - Ich bin nur der Meinung, dass eine Filteroperation als Reaktion auf eine UI-Aktion in den Code hinter der Benutzeroberfläche gehen sollte, aber Diese Entscheidung liegt wirklich bei Ihnen.