Laden...

SelectedItem im TreeView MVVM

Erstellt von Quaneu vor 11 Jahren Letzter Beitrag vor 11 Jahren 7.581 Views
Quaneu Themenstarter:in
692 Beiträge seit 2008
vor 11 Jahren
SelectedItem im TreeView MVVM

Hallo zusammen,

ich weiß das es zu diesem Thema schon 1000de Artikel gibt. Doch habe ich noch eine Frage dazu, die ich noch nicht sauber "beantworten" konnte...

Ich habe z.B. ein ViewModel PersonVM für eine Klasse Person. In dieser habe ich unter anderem zwei Properties IsSelected und IsExpanded. Diese habe ich an das TreeViewItem gebunden:


<TreeView.ItemContainerStyle>
	<Style TargetType="{x:Type TreeViewItem}">
		<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}" />
		<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
	</Style>
</TreeView.ItemContainerStyle>

Dies klappt alles wunderbar.

Doch habe ich nun folgendes Problem bzw. Probleme...
Z.B. soll in Abhängigkeit ob ein TreeViewItem selektiert ist ein Button enabled oder disabled sein. Dazu müsste aber das Viewmodel für das Fenster, in dem z.B. Personen angezeigt werden, wissen dass eine Person selektiert ist.
Oder aber ich habe z.B. links den TreeView mit Personen und wenn ich eine Person anklicke, soll rechts eine Visitenkarte angezeigt werden. Doch bisher weiß nur das ViewModel für die Person, dass es selektiert ist und eben nicht das ViewModel des Fensters, dass dann die entsprechenden Aktionen ausführt.

Ich habe es schon mit einen static Property

public static List<PersonVM> SelectedItems{get;set;}

in dem VieModel für das Fenster probiert, doch leider klappt dies auch nicht, bzw. müsste dann die Klasse Person wissen, in welchem Fenster es angezeigt wird, damit es sich z.B. in SelectedItems hinzufügen kann.

Daher meine Frage, wie bekommt man diese Probleme mit MVVM gelöst?

Schöne Grüße
Quaneu

5.742 Beiträge seit 2007
vor 11 Jahren

Hallo Quaneu,

Doch bisher weiß nur das ViewModel für die Person, dass es selektiert ist und eben nicht das ViewModel des Fensters, dass dann die entsprechenden Aktionen ausführt.

aber das ViewModel des Fensters sollte sicherlich irgendwie Zugriff auf das ViewModel mit den Personen haben.

Insofern gilt (im MainViewModel):


var selectedItems = this.TreeViewModel.Persons.Where(p => p.IsSelected);

Noch besser wäre natürlich, wenn das TreeViewModel selbst diese Selektion verwaltet (kann sogar recht generisch erfolgen) und auf Änderungen der IsSelected Property der Items lauscht.

Quaneu Themenstarter:in
692 Beiträge seit 2008
vor 11 Jahren

aber das ViewModel des Fensters sollte sicherlich irgendwie Zugriff auf das ViewModel mit den Personen haben.

Das hat es auch. Somit bekomme ich auch immer alle selektierten Personen.
Das "Problem" dabei ist, dass ja das ViewModel vom Fenster alle Personen "frägt", bist Du selektiert.
Doch das ViewModel vom Fenster soll ja z.B. bei einer Änderung der selektierten Person eine Aktion ausführen. Also genau umgekehrt.

Das hieße es würde nur über Events möglich sein...

Wäre dies der "gängige" Weg? Oder löst man dies mit Mvvm auf eine andere Art?

5.742 Beiträge seit 2007
vor 11 Jahren

Das hieße es würde nur über Events möglich sein...

Ja, im Prinzip reicht sogar PropertyChanged, wenn das TreeViewModel diese SelectedItem Eigenschaft selbst bereitstellt und sich um deren Aktualisierung kümmert (und dazu wiederrum PropertyChanged der Children abonniert).
Behilflich kann dir dabei z.B. BindableLINQ bzw. Obtics sein, die quasi ein "LINQ To Observable" bereitstellen.

Quaneu Themenstarter:in
692 Beiträge seit 2008
vor 11 Jahren

Vielen Dank für Deine Hilfe.

Jetzt bin ich leider auf ein weiteres Problem gestoßen...

Ich habe z.B. eine Instance von PersonVM, die ich in zwei TreeViews oder in einem TreeView und einem DataGrid anzeigen will. Beide (TreeView oder DataGrid) binden sich ja an IsSelected von PersonVM => selektiere ich in einem TreeView bzw. DataGrid eine Person, hat dies auswirkungen auf den anderen TreeView bzw. auf das andere DataGrid, was zu Fehlern führen kann.

=> Man kann mit diesem Ansatz nicht eine Instance in verschiedenen Views benutzten.

Gibt es hierfür eine Lösung? Den wenn ich nun Clone bekomme ja die andere View z.B. nicht mehr mit, dass sich der Name einer Person geändert hat...

Schöne Grüße
Quaneu

5.299 Beiträge seit 2008
vor 11 Jahren

selektiere ich in einem TreeView bzw. DataGrid eine Person, hat dies auswirkungen auf den anderen TreeView bzw. auf das andere DataGrid, was zu Fehlern führen kann.

Nicht unbedingt, bzw. wie mans auffasst.
Treeview und Datagrid unterstützen Multiselection, und was im DG selectiert ist, kann ebenso auch im TV selectiert sein.
Davon unterscheiden muß man das CurrentItem - das wird nur vom DG ReadWrite-mäßig unterstützt.
Beim TV die .SelectedItem-Property ist eine Art von CurrentItem, welche aber nur Readonly ist, deshalb kann man TV.SelectedItem nicht binden, jedenfalls nicht mittm Standard-Treeview.

Der frühe Apfel fängt den Wurm.

Quaneu Themenstarter:in
692 Beiträge seit 2008
vor 11 Jahren

Das Problem ist, dass ich eben nicht will, dass sich z.B. der TreeView und das Datagrid gegenseitig beeinflussen (bezüglich IsSelected).
Da es komisch ist, wenn man im Datgrid eine andere Zeile selektiert und auf einmal springt der TreeView an eine andere Stelle und zeigt dann was anderes an.

=> Ich baue ein Property IsSelectedInDataGrid und eins IsSelectedInTreeView, damit sie sich nicht gegenseitig in die Quere kommen.

Quaneu Themenstarter:in
692 Beiträge seit 2008
vor 11 Jahren

Musste leider schmerzlich feststellen, das dies auch keine Lösung ist!

Denn eigentlich dürfte PersonVM gar nicht wissen ob es selektiert ist, denn wenn man es später wieder an eine andere Itemsource bindet, kann es z.B. sein, dass es da auf einmal im neuen "Control" selektiert wird... daher finde ich diese Lösung langsam schlecht.

Des weiteren habe ich auch gelesen, dass diese Lösung nicht immer funktioniert z.B. bei Virtualizingstackpanel kann diese Lösung zu falschen Ergebnisse führen...

Was mich nun wieder zu der Frage bringt, wie man es "richtig" macht...

Schöne Grüße
Quaneu

1.378 Beiträge seit 2006
vor 11 Jahren

Hallo Quaneu,

ohne dein Problem mit gesharten Objekten in verschiedenen Auflistungscontrols jemals gehabt zu haben, verwende ich bei mir hin und wieder eine Lösung die dir hier ebenfalls helfen könnte:

Und zwar habe ich eine Wrapperklasse erstellt, welche das Objekt um die notwendigen "Gui-Spezifischen" Elemente erweitert.

z.B:


class MyListItem
{
   public object Item{get;set;}
   public bool IsSelected{get;set;}
}

In den Bindings musst du halt das .Item. noch zusätzlich anführen was aber mMn. kein Problem darstellen sollte.

Du müsstest halt dann in deinen ViewModels unterschiedliche Collections für die unterschiedlichen ListViews/TreeViews erstellen die jeweils eigene MyListItems haben die wiederum intern sich die gleichen Objekte sharen.

Lg, XXX

Quaneu Themenstarter:in
692 Beiträge seit 2008
vor 11 Jahren

Hallo xxxprod,

vielen Dank für Deine Hilfe. Genau diese Lösung habe ich gerade probiert 😃


sealed class SelectedWrapper<T> : NotificationHelper
{
	#region Private Members
	protected Boolean _isSelected;
	protected Boolean _isExpanded;
	#endregion

	#region EventHandler
	public event EventHandler<ValueEventArgs<Boolean>> SelectionChangedInTreeView;
	#endregion

	public SelectedWrapper(T content)
	{
		this.Content = content;
	}

		
	#region Properties
	public T Content { get; private set; }

	public Boolean IsSelected
	{
		get { return this._isSelected; }
		set
		{
			if (this._isSelected != value)
			{
				this._isSelected = value;
				OnSelectionChanged(this._isSelected);
				NotifyPropertyChanged("IsSelected");
			}
		}
	}

	public Boolean IsExpanded
	{
		get { return this._isExpanded; }
		set
		{
			if (this._isExpanded != value)
			{
				this._isExpanded = value;
				NotifyPropertyChanged("IsExpanded");
			}
		}
	}
	#endregion

	#region Private Methods
	private void OnSelectionChanged(Boolean isSelected)
	{
		EventHandler<ValueEventArgs<Boolean>> handler = this.SelectionChangedInTreeView;
		if (handler != null)
		{
			handler(this, new ValueEventArgs<Boolean>(isSelected));
		}
	}
	#endregion
}

Mit dem Virtualizingstackpanel muss ich mir dann noch was überlegen, aber ich denke dies ist die "schönste" Lösung.

Wobei ich jetzt natürlich für jedes ViewModel, dass diesen Wrapper benutzt wiederrum Wrapper schreiben muss, die nicht generisch sind, damit ich diese im HierarchicalDataTemplate verwenden kann... ahhhhhhhh

Grüße
Quaneu