การเชื่อมโยงข้อมูลกับ SelectedItem ใน WPF Treeview


241

ฉันจะดึงรายการที่เลือกใน WPF-treeview ได้อย่างไร ฉันต้องการทำสิ่งนี้ใน XAML เพราะฉันต้องการผูกมัน

คุณอาจคิดว่ามันเป็นSelectedItemแต่เห็นได้ชัดว่าไม่มีอยู่เป็นแบบอ่านอย่างเดียวและใช้ไม่ได้

นี่คือสิ่งที่ฉันต้องการจะทำ:

<TreeView ItemsSource="{Binding Path=Model.Clusters}" 
            ItemTemplate="{StaticResource ClusterTemplate}"
            SelectedItem="{Binding Path=Model.SelectedCluster}" />

ฉันต้องการผูกSelectedItemคุณสมบัติให้กับ Model ของฉัน

แต่นี่ทำให้ฉันเกิดข้อผิดพลาด:

คุณสมบัติ 'SelectedItem' เป็นแบบอ่านอย่างเดียวและไม่สามารถตั้งค่าได้จากมาร์กอัป

แก้ไข: ตกลงนี่คือวิธีที่ฉันแก้ไขสิ่งนี้:

<TreeView
          ItemsSource="{Binding Path=Model.Clusters}" 
          ItemTemplate="{StaticResource HoofdCLusterTemplate}"
          SelectedItemChanged="TreeView_OnSelectedItemChanged" />

และใน codebehindfile ของ xaml ของฉัน:

private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    Model.SelectedCluster = (Cluster)e.NewValue;
}

51
ผู้ชายคนนี้ sucks มันเพิ่งตีฉันด้วย ฉันมาที่นี่หวังว่าจะพบว่ามีวิธีที่ดีและฉันก็เป็นคนงี่เง่า นี่เป็นครั้งแรกที่ฉันเศร้าที่ฉันไม่ใช่คนงี่เง่า ..
อังเดรรีเนีย

6
สิ่งนี้แย่มากและสับสนกับแนวคิดที่มีผลผูกพัน
เดลต้า

หวังว่านี่จะช่วยให้ใครซักคนที่จะผูกกับไอเท็มมุมมองต้นไม้ที่ถูกเลือกเปลี่ยนโทรกลับไปที่ Icommand jacobaloysious.wordpress.com/2012/02/19/…
jacob aloysious

9
ในแง่ของการเชื่อมโยงและ MVVM โค้ดด้านหลังไม่ได้ถูก "ห้าม" แต่โค้ดด้านหลังควรสนับสนุนมุมมอง ในความเห็นของฉันจากโซลูชันอื่น ๆ ทั้งหมดที่ฉันเคยเห็นโค้ดด้านหลังเป็นตัวเลือกที่ดีกว่าอย่างมากเนื่องจากมันยังคงเกี่ยวข้องกับมุมมอง "ผูกมัด" กับโมเดลโมเดล ข้อเสียเพียงอย่างเดียวคือถ้าคุณมีทีมงานที่มีนักออกแบบทำงานใน XAML เท่านั้นโค้ดที่อยู่ด้านหลังอาจไม่ทำงาน / ถูกละเลย มันเป็นราคาขนาดเล็กสำหรับจ่ายโซลูชั่นที่ใช้เวลา 10 วินาที
nrjohnstone

หนึ่งในวิธีแก้ปัญหาที่ง่ายที่สุดน่าจะเป็น: stackoverflow.com/questions/1238304/…
JoanComasFdz

คำตอบ:


240

ฉันรู้ว่าสิ่งนี้ได้รับคำตอบที่ได้รับแล้ว แต่ฉันรวบรวมมันเพื่อแก้ไขปัญหา มันใช้แนวคิดที่คล้ายกับโซลูชันของ Delta แต่ไม่จำเป็นต้อง subclass TreeView:

public class BindableSelectedItemBehavior : Behavior<TreeView>
{
    #region SelectedItem Property

    public object SelectedItem
    {
        get { return (object)GetValue(SelectedItemProperty); }
        set { SetValue(SelectedItemProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));

    private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var item = e.NewValue as TreeViewItem;
        if (item != null)
        {
            item.SetValue(TreeViewItem.IsSelectedProperty, true);
        }
    }

    #endregion

    protected override void OnAttached()
    {
        base.OnAttached();

        this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        if (this.AssociatedObject != null)
        {
            this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
        }
    }

    private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        this.SelectedItem = e.NewValue;
    }
}

จากนั้นคุณสามารถใช้สิ่งนี้ใน XAML ของคุณเป็น:

<TreeView>
    <e:Interaction.Behaviors>
        <behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
    </e:Interaction.Behaviors>
</TreeView>

หวังว่ามันจะช่วยใครซักคน!


5
ในขณะที่เบรนต์ชี้ให้เห็นฉันจำเป็นต้องเพิ่ม Mode = TwoWay เข้ากับการเชื่อมโยง ฉันไม่ใช่ "Blender" ดังนั้นจึงไม่คุ้นเคยกับคลาส Behavior <> จาก System.Windows.Interactivity การประกอบเป็นส่วนหนึ่งของ Expression Blend สำหรับผู้ที่ไม่ต้องการซื้อ / ติดตั้งรุ่นทดลองเพื่อรับชุดประกอบนี้คุณสามารถดาวน์โหลด BlendSDK ซึ่งรวมถึง System.Windows.Interactivity BlendSDK 3 สำหรับ 3.5 ... ฉันคิดว่ามันคือ BlendSDK 4 สำหรับ 4.0 หมายเหตุ: นี่อนุญาตให้คุณเลือกรายการที่ต้องการเท่านั้นไม่อนุญาตให้คุณตั้งค่ารายการที่เลือก
Mike Rowley

4
คุณยังสามารถแทนที่ UIPropertyMetadata โดย FrameworkPropertyMetadata (null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged));
Filimindji

3
นี่จะเป็นวิธีการในการแก้ปัญหา: stackoverflow.com/a/18700099/4227
bitbonk

2
@Lukas ตามที่แสดงในตัวอย่างโค้ด XAML ด้านบน เพียงแทนที่{Binding SelectedItem, Mode=TwoWay}ด้วย{Binding MyViewModelField, Mode=TwoWay}
Steve Greatrex

4
@Pascal มันxmlns:e="http://schemas.microsoft.com/expression/2010/interactivity"
Steve Greatrex

46

มีคุณสมบัตินี้อยู่: TreeView.SelectedItem

แต่เป็นแบบอ่านอย่างเดียวดังนั้นคุณไม่สามารถกำหนดผ่านการโยงได้


ฉันยอมรับคำตอบนี้เพราะที่นั่นฉันพบลิงค์นี้ซึ่งให้คำตอบของฉันเอง: msdn.microsoft.com/en-us/library/ms788714.aspx
Natrium

1
ดังนั้นฉันจึงสามารถTreeView.SelectedItemส่งผลกระทบต่อสถานที่ให้บริการในรูปแบบเมื่อผู้ใช้เลือกรายการ (aka OneWayToSource)?
Shimmy Weitzhandler

43

ตอบด้วยคุณสมบัติที่แนบมาและไม่มีการพึ่งพาภายนอกควรจะต้องเกิดขึ้น!

คุณสามารถสร้างคุณสมบัติที่แนบมาซึ่งสามารถ bindable และมี getter และ setter:

public class TreeViewHelper
{
    private static Dictionary<DependencyObject, TreeViewSelectedItemBehavior> behaviors = new Dictionary<DependencyObject, TreeViewSelectedItemBehavior>();

    public static object GetSelectedItem(DependencyObject obj)
    {
        return (object)obj.GetValue(SelectedItemProperty);
    }

    public static void SetSelectedItem(DependencyObject obj, object value)
    {
        obj.SetValue(SelectedItemProperty, value);
    }

    // Using a DependencyProperty as the backing store for SelectedItem.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged));

    private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (!(obj is TreeView))
            return;

        if (!behaviors.ContainsKey(obj))
            behaviors.Add(obj, new TreeViewSelectedItemBehavior(obj as TreeView));

        TreeViewSelectedItemBehavior view = behaviors[obj];
        view.ChangeSelectedItem(e.NewValue);
    }

    private class TreeViewSelectedItemBehavior
    {
        TreeView view;
        public TreeViewSelectedItemBehavior(TreeView view)
        {
            this.view = view;
            view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
        }

        internal void ChangeSelectedItem(object p)
        {
            TreeViewItem item = (TreeViewItem)view.ItemContainerGenerator.ContainerFromItem(p);
            item.IsSelected = true;
        }
    }
}

เพิ่มการประกาศเนมสเปซที่มีคลาสนั้นใน XAML ของคุณและผูกดังต่อไปนี้ (ในพื้นที่เป็นวิธีที่ฉันตั้งชื่อการประกาศเนมสเปซ):

        <TreeView ItemsSource="{Binding Path=Root.Children}" local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}">

    </TreeView>

ตอนนี้คุณสามารถผูกไอเท็มที่เลือกและตั้งค่าในโมเดลมุมมองของคุณเพื่อเปลี่ยนแปลงโดยทางโปรแกรมหากความต้องการนั้นเกิดขึ้น แน่นอนนี่คือสมมติว่าคุณใช้ INotifyPropertyChanged กับคุณสมบัติเฉพาะนั้น


4
+1, คำตอบที่ดีที่สุดในกระทู้นี้ ไม่มีการพึ่งพา System.Windows.Interactivity และอนุญาตให้เชื่อมโยงสองทาง (การตั้งค่าโดยทางโปรแกรมในสภาพแวดล้อม MVVM) สมบูรณ์
Chris Ray

5
ปัญหาเกี่ยวกับวิธีการนี้คือพฤติกรรมจะเริ่มทำงานเมื่อรายการที่เลือกได้รับการตั้งค่าครั้งเดียวผ่านการเชื่อมโยง (เช่นจาก ViewModel) หากค่าเริ่มต้นใน VM เป็นโมฆะการโยงจะไม่อัปเดตค่า DP และพฤติกรรมจะไม่เปิดใช้งาน คุณสามารถแก้ไขได้โดยใช้รายการที่เลือกเริ่มต้นอื่น (เช่นรายการที่ไม่ถูกต้อง)
Mark

6
@ Mark: เพียงแค่ใช้วัตถุใหม่ () แทนค่า null ด้านบนเมื่อสร้างอินสแตนซ์ UIPropertyMetadata ให้กับอินสแตนซ์ของคุณสมบัติที่แนบมา ปัญหาน่าจะหมดไปแล้ว ...
เคิลบอย

2
การส่งไปยัง TreeViewItem ล้มเหลวสำหรับฉันฉันถือว่าเนื่องจากฉันใช้ HierarchicalDataTemplate ที่ใช้จากแหล่งข้อมูลตามประเภทข้อมูล แต่ถ้าคุณลบ ChangeSelectedItem แล้วเชื่อมโยงกับ viewmodel และการดึงรายการทำงานได้ดี
Casey Sebben

1
ฉันยังมีปัญหากับนักแสดงถึง TreeViewItem ด้วย ที่จุดนั้น ItemContainerGenerator มีเพียงการอ้างอิงไปยังรายการรูท แต่ฉันต้องการที่จะได้รับรายการที่ไม่ใช่รูทด้วย หากคุณส่งการอ้างอิงไปยังหมายเลขหนึ่งการร่ายล้มเหลวและส่งคืนค่าว่าง ไม่แน่ใจว่าจะแก้ไขได้อย่างไร
Bob Tway

39

ฉันพบวิธีแก้ปัญหา มันเป็นระเบียบดังนั้น MVVM จึงทำงาน

เพิ่มคลาสนี้ก่อน:

public class ExtendedTreeView : TreeView
{
    public ExtendedTreeView()
        : base()
    {
        this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(___ICH);
    }

    void ___ICH(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        if (SelectedItem != null)
        {
            SetValue(SelectedItem_Property, SelectedItem);
        }
    }

    public object SelectedItem_
    {
        get { return (object)GetValue(SelectedItem_Property); }
        set { SetValue(SelectedItem_Property, value); }
    }
    public static readonly DependencyProperty SelectedItem_Property = DependencyProperty.Register("SelectedItem_", typeof(object), typeof(ExtendedTreeView), new UIPropertyMetadata(null));
}

และเพิ่มลงใน xaml ของคุณ:

 <local:ExtendedTreeView ItemsSource="{Binding Items}" SelectedItem_="{Binding Item, Mode=TwoWay}">
 .....
 </local:ExtendedTreeView>

3
นี่คือสิ่งเดียวที่เข้ามาใกล้ฉันแล้ว ฉันชอบวิธีนี้มาก
Rachael

1
ไม่ทราบว่าทำไม แต่มันก็ไม่ทำงานสำหรับฉัน :( ผมประสบความสำเร็จที่จะได้รับรายการที่เลือกมาจากต้นไม้ แต่ไม่ในทางกลับกัน - เพื่อเปลี่ยนรายการที่เลือกจากนอกต้นไม้.
Erez

มันจะเป็น neater เล็กน้อยเพื่อตั้งค่าคุณสมบัติการพึ่งพาเป็น BindsTwoWayByDefault จากนั้นคุณไม่จำเป็นต้องระบุ TwoWay ใน XAML
Stephen Holt

นี่เป็นวิธีที่ดีที่สุด มันไม่ได้ใช้การอ้างอิงการโต้ตอบมันไม่ได้ใช้รหัสที่อยู่เบื้องหลังมันไม่ได้มีหน่วยความจำรั่วเหมือนพฤติกรรมบางอย่างมี ขอบคุณ.
Alexandru Dicu

ดังที่ได้กล่าวมาแล้ววิธีนี้ไม่ได้ผลกับการผูก 2 ทาง หากคุณตั้งค่าใน viewmodel การเปลี่ยนแปลงจะไม่แพร่กระจายไปยัง TreeView
ริชาร์ดมัวร์

25

มันตอบมากกว่าที่ OP คาดไว้เล็กน้อย แต่ฉันหวังว่ามันจะช่วยได้บ้าง

หากคุณต้องการดำเนินการICommandทุกครั้งที่มีการSelectedItemเปลี่ยนแปลงคุณสามารถผูกคำสั่งกับเหตุการณ์และการใช้คุณสมบัติSelectedItemในViewModelไม่จำเป็นอีกต่อไป

โดยทำดังนี้

1- เพิ่มการอ้างอิงถึง System.Windows.Interactivity

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

2- ผูกคำสั่งกับเหตุการณ์ SelectedItemChanged

<TreeView x:Name="myTreeView" Margin="1"
            ItemsSource="{Binding Directories}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectedItemChanged">
            <i:InvokeCommandAction Command="{Binding SomeCommand}"
                                   CommandParameter="
                                            {Binding ElementName=myTreeView
                                             ,Path=SelectedItem}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <TreeView.ItemTemplate>
           <!-- ... -->
    </TreeView.ItemTemplate>
</TreeView>

3
การอ้างอิงSystem.Windows.Interactivityสามารถติดตั้งได้จาก NuGet: nuget.org/packages/System.Windows.Interactivity.WPF
Junle Li

ฉันพยายามแก้ไขปัญหานี้มาหลายชั่วโมงฉันใช้งานแล้ว แต่คำสั่งของฉันไม่ทำงานโปรดช่วยฉันได้ไหม
Alfie

1
มีการแนะนำ XAML พฤติกรรมสำหรับ WPF โดยไมโครซอฟท์ในตอนท้ายของปี 2018 System.Windows.Interactivityที่จะสามารถนำมาใช้แทนของ มันใช้งานได้สำหรับฉัน (ลองกับ. NET Core project) ในการตั้งสิ่งที่ขึ้นเพียงเพิ่ม Microsoft.Xaml.Behaviors.Wpfแพคเกจ nuget เปลี่ยน namespace xmlns:i="http://schemas.microsoft.com/xaml/behaviors"ไป เพื่อรับข้อมูลเพิ่มเติม - โปรดดูบล็อก
rychlmoj

19

สามารถทำได้ในลักษณะ 'ดีกว่า' โดยใช้การโยงเท่านั้นและ EventToCommand ของไลบรารี GalaSoft MVVM Light ใน VM ของคุณเพิ่มคำสั่งที่จะถูกเรียกเมื่อรายการที่เลือกมีการเปลี่ยนแปลงและเริ่มต้นคำสั่งเพื่อดำเนินการสิ่งที่จำเป็น ในตัวอย่างนี้ฉันใช้ RelayCommand และเพิ่งจะตั้งค่าคุณสมบัติ SelectedCluster

public class ViewModel
{
    public ViewModel()
    {
        SelectedClusterChanged = new RelayCommand<Cluster>( c => SelectedCluster = c );
    }

    public RelayCommand<Cluster> SelectedClusterChanged { get; private set; } 

    public Cluster SelectedCluster { get; private set; }
}

จากนั้นเพิ่มลักษณะการทำงาน EventToCommand ใน xaml ของคุณ มันง่ายมากที่จะใช้การผสมผสาน

<TreeView
      x:Name="lstClusters"
      ItemsSource="{Binding Path=Model.Clusters}" 
      ItemTemplate="{StaticResource HoofdCLusterTemplate}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectedItemChanged">
            <GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding SelectedClusterChanged}" CommandParameter="{Binding ElementName=lstClusters,Path=SelectedValue}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TreeView>

นี่เป็นทางออกที่ดีโดยเฉพาะถ้าคุณใช้ชุดเครื่องมือ MvvmLight อยู่แล้ว อย่างไรก็ตามมันไม่ได้แก้ปัญหาการตั้งค่าโหนดที่เลือกและให้ TreeView อัพเดตสิ่งที่เลือก
keft

12

ทุกอย่างซับซ้อน ... ไปกับ Caliburn Micro (http://caliburnmicro.codeplex.com/)

ดู:

<TreeView Micro:Message.Attach="[Event SelectedItemChanged] = [Action SetSelectedItem($this.SelectedItem)]" />

ViewModel:

public void SetSelectedItem(YourNodeViewModel item) {}; 

5
ใช่ ... และสถานที่ที่เป็นส่วนที่ว่าชุด SelectedItem ในTreeView ?
mnn

คาลิเบอร์เป็นคนดีและสง่างาม ทำงานได้ค่อนข้างง่ายสำหรับลำดับชั้นซ้อนกัน
Purusartha

8

ฉันเจอหน้านี้เพื่อค้นหาคำตอบเดียวกับผู้แต่งดั้งเดิมและการพิสูจน์ว่ามีวิธีมากกว่าหนึ่งวิธีการแก้ปัญหาสำหรับฉันนั้นง่ายกว่าคำตอบที่ให้ไว้ที่นี่ดังนั้นฉันคิดว่าฉันอาจเพิ่ม กับกอง

แรงจูงใจในการผูกคือการทำให้มันดี & MVVM การใช้งานที่เป็นไปได้ของ ViewModel คือการมีคุณสมบัติที่มีชื่อเช่น "CurrentThingy" และที่อื่น DataContext ในบางสิ่งถูกผูกไว้กับ "CurrentThingy"

แทนที่จะทำตามขั้นตอนเพิ่มเติมที่ต้องการ (เช่นพฤติกรรมที่กำหนดเอง, การควบคุมของบุคคลที่สาม) เพื่อสนับสนุนการเชื่อมโยงที่ดีจาก TreeView ไปยังแบบจำลองของฉันและจากสิ่งอื่นไปยังแบบจำลองของฉันโซลูชันของฉันคือการใช้องค์ประกอบอย่างง่าย TreeView.SelectedItem แทนที่จะผูกสิ่งอื่นเข้ากับ ViewModel ของฉันดังนั้นจึงข้ามงานพิเศษที่จำเป็น

XAML:

<TreeView x:Name="myTreeView" ItemsSource="{Binding MyThingyCollection}">
.... stuff
</TreeView>

<!-- then.. somewhere else where I want to see the currently selected TreeView item: -->

<local:MyThingyDetailsView 
       DataContext="{Binding ElementName=myTreeView, Path=SelectedItem}" />

แน่นอนว่าเป็นสิ่งที่ดีสำหรับการอ่านรายการที่เลือกในปัจจุบัน แต่ไม่ได้ตั้งค่าซึ่งเป็นสิ่งที่ฉันต้องการ


1
ท้องถิ่นคืออะไร: MyThingyDetailsView ฉันได้รับข้อมูลภายในเครื่อง: MyThingyDetailsView เก็บรายการที่เลือก แต่โมเดลการดูของคุณจะรับข้อมูลนี้ได้อย่างไร ดูเหมือนว่าเป็นวิธีที่ดีสะอาดในการทำเช่นนี้ แต่ฉันต้องการข้อมูลเพิ่มเติมเพียงเล็กน้อย ...
Bob Horn

ท้องถิ่น: MyThingyDetailsView เป็นเพียง UserControl ที่เต็มไปด้วย XAML ประกอบขึ้นเป็นมุมมองรายละเอียดเกี่ยวกับหนึ่งอินสแตนซ์ "thingy" มันฝังอยู่ตรงกลางของมุมมองอื่นเป็นเนื้อหา w / DataContext ของมุมมองนี้เป็นรายการมุมมองต้นไม้ที่เลือกในปัจจุบันโดยใช้องค์ประกอบผูกพัน
Wes

6

คุณอาจสามารถใช้คุณสมบัติ TreeViewItem.IsSelected


ฉันคิดว่านี่อาจเป็นคำตอบที่ถูกต้อง แต่ฉันต้องการดูตัวอย่างหรือคำแนะนำวิธีปฏิบัติที่ดีที่สุดเกี่ยวกับวิธีที่คุณสมบัติ IsSelected ของรายการถูกส่งผ่านไปยัง TreeView
anhoppe

3

นอกจากนี้ยังมีวิธีในการสร้างคุณสมบัติ XAML bindable SelectedItem โดยไม่ต้องใช้ Interaction.Behaviors

public static class BindableSelectedItemHelper
{
    #region Properties

    public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(BindableSelectedItemHelper),
        new FrameworkPropertyMetadata(null, OnSelectedItemPropertyChanged));

    public static readonly DependencyProperty AttachProperty = DependencyProperty.RegisterAttached("Attach", typeof(bool), typeof(BindableSelectedItemHelper), new PropertyMetadata(false, Attach));

    private static readonly DependencyProperty IsUpdatingProperty = DependencyProperty.RegisterAttached("IsUpdating", typeof(bool), typeof(BindableSelectedItemHelper));

    #endregion

    #region Implementation

    public static void SetAttach(DependencyObject dp, bool value)
    {
        dp.SetValue(AttachProperty, value);
    }

    public static bool GetAttach(DependencyObject dp)
    {
        return (bool)dp.GetValue(AttachProperty);
    }

    public static string GetSelectedItem(DependencyObject dp)
    {
        return (string)dp.GetValue(SelectedItemProperty);
    }

    public static void SetSelectedItem(DependencyObject dp, object value)
    {
        dp.SetValue(SelectedItemProperty, value);
    }

    private static bool GetIsUpdating(DependencyObject dp)
    {
        return (bool)dp.GetValue(IsUpdatingProperty);
    }

    private static void SetIsUpdating(DependencyObject dp, bool value)
    {
        dp.SetValue(IsUpdatingProperty, value);
    }

    private static void Attach(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        TreeListView treeListView = sender as TreeListView;
        if (treeListView != null)
        {
            if ((bool)e.OldValue)
                treeListView.SelectedItemChanged -= SelectedItemChanged;

            if ((bool)e.NewValue)
                treeListView.SelectedItemChanged += SelectedItemChanged;
        }
    }

    private static void OnSelectedItemPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        TreeListView treeListView = sender as TreeListView;
        if (treeListView != null)
        {
            treeListView.SelectedItemChanged -= SelectedItemChanged;

            if (!(bool)GetIsUpdating(treeListView))
            {
                foreach (TreeViewItem item in treeListView.Items)
                {
                    if (item == e.NewValue)
                    {
                        item.IsSelected = true;
                        break;
                    }
                    else
                       item.IsSelected = false;                        
                }
            }

            treeListView.SelectedItemChanged += SelectedItemChanged;
        }
    }

    private static void SelectedItemChanged(object sender, RoutedEventArgs e)
    {
        TreeListView treeListView = sender as TreeListView;
        if (treeListView != null)
        {
            SetIsUpdating(treeListView, true);
            SetSelectedItem(treeListView, treeListView.SelectedItem);
            SetIsUpdating(treeListView, false);
        }
    }
    #endregion
}

จากนั้นคุณสามารถใช้สิ่งนี้ใน XAML ของคุณเป็น:

<TreeView  helper:BindableSelectedItemHelper.Attach="True" 
           helper:BindableSelectedItemHelper.SelectedItem="{Binding SelectedItem, Mode=TwoWay}">

3

ฉันลองวิธีแก้ปัญหาทั้งหมดของคำถามนี้ ไม่มีใครแก้ปัญหาของฉันได้อย่างเต็มที่ ดังนั้นฉันคิดว่ามันจะดีกว่าถ้าใช้คลาสที่สืบทอดมาพร้อมกับคุณสมบัติที่กำหนดใหม่ SelectedItem มันจะทำงานได้อย่างสมบูรณ์ถ้าคุณเลือกองค์ประกอบต้นไม้จาก GUI และถ้าคุณตั้งค่าคุณสมบัตินี้ในรหัสของคุณ

public class TreeViewEx : TreeView
{
    public TreeViewEx()
    {
        this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(TreeViewEx_SelectedItemChanged);
    }

    void TreeViewEx_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        this.SelectedItem = e.NewValue;
    }

    #region SelectedItem

    /// <summary>
    /// Gets or Sets the SelectedItem possible Value of the TreeViewItem object.
    /// </summary>
    public new object SelectedItem
    {
        get { return this.GetValue(TreeViewEx.SelectedItemProperty); }
        set { this.SetValue(TreeViewEx.SelectedItemProperty, value); }
    }

    // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
    public new static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register("SelectedItem", typeof(object), typeof(TreeViewEx),
        new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedItemProperty_Changed));

    static void SelectedItemProperty_Changed(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        TreeViewEx targetObject = dependencyObject as TreeViewEx;
        if (targetObject != null)
        {
            TreeViewItem tvi = targetObject.FindItemNode(targetObject.SelectedItem) as TreeViewItem;
            if (tvi != null)
                tvi.IsSelected = true;
        }
    }                                               
    #endregion SelectedItem   

    public TreeViewItem FindItemNode(object item)
    {
        TreeViewItem node = null;
        foreach (object data in this.Items)
        {
            node = this.ItemContainerGenerator.ContainerFromItem(data) as TreeViewItem;
            if (node != null)
            {
                if (data == item)
                    break;
                node = FindItemNodeInChildren(node, item);
                if (node != null)
                    break;
            }
        }
        return node;
    }

    protected TreeViewItem FindItemNodeInChildren(TreeViewItem parent, object item)
    {
        TreeViewItem node = null;
        bool isExpanded = parent.IsExpanded;
        if (!isExpanded) //Can't find child container unless the parent node is Expanded once
        {
            parent.IsExpanded = true;
            parent.UpdateLayout();
        }
        foreach (object data in parent.Items)
        {
            node = parent.ItemContainerGenerator.ContainerFromItem(data) as TreeViewItem;
            if (data == item && node != null)
                break;
            node = FindItemNodeInChildren(node, item);
            if (node != null)
                break;
        }
        if (node == null && parent.IsExpanded != isExpanded)
            parent.IsExpanded = isExpanded;
        if (node != null)
            parent.IsExpanded = true;
        return node;
    }
} 

มันจะเร็วกว่านี้หาก UpdateLayout () และไม่ได้ถูกเรียกสำหรับบางโหนด เมื่อใดที่ไม่จำเป็นต้องโทรไปที่ UpdateLayout () และ Is Expanded? เมื่อรายการต้นไม้ถูกเข้าชมก่อนหน้านี้ จะรู้ได้อย่างไร? ContainerFromItem () ส่งคืนค่า null สำหรับโหนดที่ไม่ได้เข้าชม ดังนั้นเราสามารถขยายโหนดหลักได้เฉพาะเมื่อ ContainerFromItem () ส่งคืนค่า null สำหรับเด็ก ๆ
CoperNick

3

ความต้องการของฉันมีไว้สำหรับโซลูชันที่ใช้ PRISM-MVVM ซึ่งต้องการ TreeView และวัตถุที่ถูกผูกไว้เป็นประเภท Collection <> ดังนั้นจึงต้องมี HierarchicalDataTemplate BindableSelectedItemBehavior เริ่มต้นจะไม่สามารถระบุเด็ก TreeViewItem เพื่อให้มันทำงานในสถานการณ์นี้

public class BindableSelectedItemBehavior : Behavior<TreeView>
{
    #region SelectedItem Property

    public object SelectedItem
    {
        get { return (object)GetValue(SelectedItemProperty); }
        set { SetValue(SelectedItemProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));

    private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var behavior = sender as BindableSelectedItemBehavior;
        if (behavior == null) return;
        var tree = behavior.AssociatedObject;
        if (tree == null) return;
        if (e.NewValue == null)
            foreach (var item in tree.Items.OfType<TreeViewItem>())
                item.SetValue(TreeViewItem.IsSelectedProperty, false);
        var treeViewItem = e.NewValue as TreeViewItem;
        if (treeViewItem != null)
            treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
        else
        {
            var itemsHostProperty = tree.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            if (itemsHostProperty == null) return;
            var itemsHost = itemsHostProperty.GetValue(tree, null) as Panel;
            if (itemsHost == null) return;
            foreach (var item in itemsHost.Children.OfType<TreeViewItem>())
            {
                if (WalkTreeViewItem(item, e.NewValue)) 
                    break;
            }
        }
    }

    public static bool WalkTreeViewItem(TreeViewItem treeViewItem, object selectedValue)
    {
        if (treeViewItem.DataContext == selectedValue)
        {
            treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
            treeViewItem.Focus();
            return true;
        }
        var itemsHostProperty = treeViewItem.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        if (itemsHostProperty == null) return false;
        var itemsHost = itemsHostProperty.GetValue(treeViewItem, null) as Panel;
        if (itemsHost == null) return false;
        foreach (var item in itemsHost.Children.OfType<TreeViewItem>())
        {
            if (WalkTreeViewItem(item, selectedValue))
                break;
        }
        return false;
    }
    #endregion

    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        if (this.AssociatedObject != null)
        {
            this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
        }
    }

    private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        this.SelectedItem = e.NewValue;
    }
}

สิ่งนี้ช่วยให้วนซ้ำองค์ประกอบทั้งหมดโดยไม่คำนึงถึงระดับ


ขอบคุณ! นี่เป็นสิ่งเดียวที่ใช้ได้กับสถานการณ์ของฉันซึ่งไม่เหมือนของคุณ
Robert

ทำงานได้เป็นอย่างดีและไม่ก่อให้เกิดการคัดเลือก / การผูกขยายที่จะได้รับสับสน
Rusty

2

ฉันขอแนะนำการเพิ่มให้กับพฤติกรรมที่จัดทำโดย Steve Greatrex พฤติกรรมของเขาไม่ได้สะท้อนการเปลี่ยนแปลงจากแหล่งที่มาเพราะมันอาจไม่ใช่คอลเลกชันของ TreeViewItems ดังนั้นมันจึงเป็นเรื่องของการค้นหา TreeViewItem ในทรีซึ่ง datacontext นั้นถูกเลือกค่าจากแหล่งที่มา TreeView มีคุณสมบัติที่มีการป้องกันที่เรียกว่า "ItemsHost" ซึ่งเก็บคอลเลกชัน TreeViewItem เราสามารถผ่านการไตร่ตรองและเดินไปตามต้นไม้เพื่อค้นหารายการที่เลือก

private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var behavior = sender as BindableSelectedItemBehaviour;

        if (behavior == null) return;

        var tree = behavior.AssociatedObject;

        if (tree == null) return;

        if (e.NewValue == null) 
            foreach (var item in tree.Items.OfType<TreeViewItem>())
                item.SetValue(TreeViewItem.IsSelectedProperty, false);

        var treeViewItem = e.NewValue as TreeViewItem; 
        if (treeViewItem != null)
        {
            treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
        }
        else
        {
            var itemsHostProperty = tree.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

            if (itemsHostProperty == null) return;

            var itemsHost = itemsHostProperty.GetValue(tree, null) as Panel;

            if (itemsHost == null) return;

            foreach (var item in itemsHost.Children.OfType<TreeViewItem>())
                if (WalkTreeViewItem(item, e.NewValue)) break;
        }
    }

    public static bool WalkTreeViewItem(TreeViewItem treeViewItem, object selectedValue) {
        if (treeViewItem.DataContext == selectedValue)
        {
            treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
            treeViewItem.Focus();
            return true;
        }

        foreach (var item in treeViewItem.Items.OfType<TreeViewItem>())
            if (WalkTreeViewItem(item, selectedValue)) return true;

        return false;
    }

วิธีนี้พฤติกรรมใช้งานได้กับการผูกสองทาง หรือเป็นไปได้ที่จะย้ายรายการ ItemsHost ไปยังเมธอด OnAttached ของ Behavior ซึ่งจะช่วยประหยัดค่าใช้จ่ายในการใช้การสะท้อนกลับทุกครั้งที่มีการอัพเดตการโยง


2

WPF MVVM TreeView SelectedItem

... เป็นคำตอบที่ดีกว่า แต่ไม่ได้กล่าวถึงวิธีการรับ / ตั้งค่า SelectedItem ใน ViewModel

  1. เพิ่มคุณสมบัติบูลีน IsSelected ให้กับ ItemViewModel ของคุณและผูกเข้ากับมันใน Style Setter สำหรับ TreeViewItem
  2. เพิ่มคุณสมบัติ SelectedItem ให้กับ ViewModel ของคุณที่ใช้เป็น DataContext สำหรับ TreeView นี่คือชิ้นส่วนที่ขาดหายไปในโซลูชันด้านบน
    'ItemVM ...
    คุณสมบัติสาธารณะถูกเลือกเป็นบูลีน
        ได้รับ
            ส่งคืน _func.SelectedNode Is Me
        สิ้นสุดรับ
        ตั้งค่า (เป็นค่าบูลีน)
            ถ้า IsSelected ค่าแล้ว
                _func.SelectedNode = ถ้า (ค่าฉันไม่มีอะไร)
            สิ้นสุดถ้า
            RaisePropertyChange ()
        จบชุด
    สิ้นสุดคุณสมบัติ
    'TreeVM ...
    คุณสมบัติสาธารณะที่เลือกเป็นรายการ ItemMM
        ได้รับ
            ส่งคืน _selectedItem
        สิ้นสุดรับ
        ตั้งค่า (ค่าเป็น ItemVM)
            ถ้า _selectedItem เป็นค่าแล้ว
                กลับ
            สิ้นสุดถ้า
            Dim prev = _selectedItem
            _selectedItem = ค่า
            ถ้า prev ไม่มีอะไรแล้ว
                prev.IsSelected = เท็จ
            สิ้นสุดถ้า
            ถ้า _selectedItem ไม่เป็นอะไรเลย
                _selectedItem.IsSelected = True
            สิ้นสุดถ้า
        จบชุด
    สิ้นสุดคุณสมบัติ
<TreeView ItemsSource="{Binding Path=TreeVM}" 
          BorderBrush="Transparent">
    <TreeView.ItemContainerStyle>
        <Style TargetType="TreeViewItem">
            <Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
        </Style>
    </TreeView.ItemContainerStyle>
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <TextBlock Text="{Binding Name}"/>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

1

หลังจากศึกษาอินเทอร์เน็ตเป็นเวลาหนึ่งวันฉันพบโซลูชันของตัวเองสำหรับการเลือกรายการหลังจากสร้างมุมมองแบบต้นไม้ปกติในสภาพแวดล้อม WPF / C # ปกติ

private void BuildSortTree(int sel)
        {
            MergeSort.Items.Clear();
            TreeViewItem itTemp = new TreeViewItem();
            itTemp.Header = SortList[0];
            MergeSort.Items.Add(itTemp);
            TreeViewItem prev;
            itTemp.IsExpanded = true;
            if (0 == sel) itTemp.IsSelected= true;
            prev = itTemp;
            for(int i = 1; i<SortList.Count; i++)
            {

                TreeViewItem itTempNEW = new TreeViewItem();
                itTempNEW.Header = SortList[i];
                prev.Items.Add(itTempNEW);
                itTempNEW.IsExpanded = true;
                if (i == sel) itTempNEW.IsSelected = true;
                prev = itTempNEW ;
            }
        }

1

นอกจากนี้ยังสามารถทำได้โดยใช้คุณสมบัติ IsSelected ของรายการ TreeView นี่คือวิธีที่ฉันจัดการ

public delegate void TreeviewItemSelectedHandler(TreeViewItem item);
public class TreeViewItem
{      
  public static event TreeviewItemSelectedHandler OnItemSelected = delegate { };
  public bool IsSelected 
  {
    get { return isSelected; }
    set 
    { 
      isSelected = value;
      if (value)
        OnItemSelected(this);
    }
  }
}

จากนั้นใน ViewModel ที่มีข้อมูลที่ TreeView ของคุณถูกผูกไว้เพียงสมัครรับข้อมูลเหตุการณ์ในคลาส TreeViewItem

TreeViewItem.OnItemSelected += TreeViewItemSelected;

และสุดท้ายใช้ตัวจัดการนี้ใน ViewModel เดียวกัน

private void TreeViewItemSelected(TreeViewItem item)
{
  //Do something
}

และการผูกพันแน่นอน

<Setter Property="IsSelected" Value="{Binding IsSelected}" />    

นี่เป็นวิธีแก้ปัญหาที่ไม่ได้รับการจัดอันดับ ด้วยการเปลี่ยนวิธีคิดและเชื่อมโยงคุณสมบัติ IsSelected ของแต่ละองค์ประกอบของมุมมองแบบต้นไม้และทำให้เหตุการณ์ IsSelected ทำให้คุณได้รับการใช้งานในตัวซึ่งทำงานได้ดีกับการผูกสองทาง ฉันได้ลองวิธีแก้ปัญหาที่เสนอมาหลายข้อแล้วและนี่เป็นครั้งแรกที่ใช้งานได้ ซับซ้อนเพียงเล็กน้อยในการเดินสาย ขอบคุณ
Richard Moore

1

ฉันรู้ว่าหัวข้อนี้มีอายุ 10 ปี แต่ปัญหายังคงมีอยู่ ....

คำถามเดิมคือ 'เพื่อดึง' รายการที่เลือก ฉันยังต้อง "รับ" รายการที่เลือกใน viewmodel ของฉัน (ไม่ได้ตั้งค่า) จากคำตอบทั้งหมดในหัวข้อนี้คำตอบโดย 'Wes' เป็นคำตอบเดียวที่เข้าหาปัญหาแตกต่างกัน: หากคุณสามารถใช้ 'รายการที่เลือก' เป็นเป้าหมายสำหรับการรวบรวมข้อมูลใช้เป็นแหล่งข้อมูลสำหรับการรวบรวมข้อมูล Wes ทำกับอสังหาริมทรัพย์ในมุมมองอื่นฉันจะทำกับอสังหาริมทรัพย์ใน viewmodel:

เราต้องการสองสิ่ง:

  • สร้างคุณสมบัติการพึ่งพาใน viewmodel (ในกรณีของฉันพิมพ์ 'MyObject' เป็น treeview ของฉันถูกผูกไว้กับวัตถุประเภท 'MyObject')
  • เชื่อมโยงจาก Treeview.SelectedItem เข้ากับคุณสมบัตินี้ใน Constructor ของ View (ใช่ว่าเป็นโค้ดที่อยู่ด้านหลัง แต่เป็นไปได้ว่าคุณจะเริ่มต้น datacontext ที่นั่นด้วย)

Viewmodel:

public static readonly DependencyProperty SelectedTreeViewItemProperty = DependencyProperty.Register("SelectedTreeViewItem", typeof(MyObject), typeof(MyViewModel), new PropertyMetadata(OnSelectedTreeViewItemChanged));

    private static void OnSelectedTreeViewItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        (d as MyViewModel).OnSelectedTreeViewItemChanged(e);
    }

    private void OnSelectedTreeViewItemChanged(DependencyPropertyChangedEventArgs e)
    {
        //do your stuff here
    }

    public MyObject SelectedWorkOrderTreeViewItem
    {
        get { return (MyObject)GetValue(SelectedTreeViewItemProperty); }
        set { SetValue(SelectedTreeViewItemProperty, value); }
    }

ดูตัวสร้าง:

Binding binding = new Binding("SelectedItem")
        {
            Source = treeView, //name of tree view in xaml
            Mode = BindingMode.OneWay
        };

        BindingOperations.SetBinding(DataContext, MyViewModel.SelectedTreeViewItemProperty, binding);

0

(เราเห็นด้วยทั้งหมดว่า TreeView ถูกจับอย่างชัดเจนในส่วนที่เกี่ยวกับปัญหานี้การผูกกับ SelectedItem จะเห็นได้ชัด ถอนหายใจ )

ฉันต้องการโซลูชันในการโต้ตอบอย่างถูกต้องกับคุณสมบัติ IsSelected ของ TreeViewItem ดังนั้นนี่คือวิธีที่ฉันทำ:

// the Type CustomThing needs to implement IsSelected with notification
// for this to work.
public class CustomTreeView : TreeView
{
    public CustomThing SelectedCustomThing
    {
        get
        {
            return (CustomThing)GetValue(SelectedNode_Property);
        }
        set
        {
            SetValue(SelectedNode_Property, value);
            if(value != null) value.IsSelected = true;
        }
    }

    public static DependencyProperty SelectedNode_Property =
        DependencyProperty.Register(
            "SelectedCustomThing",
            typeof(CustomThing),
            typeof(CustomTreeView),
            new FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.None,
                SelectedNodeChanged));

    public CustomTreeView(): base()
    {
        this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(SelectedItemChanged_CustomHandler);
    }

    void SelectedItemChanged_CustomHandler(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        SetValue(SelectedNode_Property, SelectedItem);
    }

    private static void SelectedNodeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var treeView = d as CustomTreeView;
        var newNode = e.NewValue as CustomThing;

        treeView.SelectedCustomThing = (CustomThing)e.NewValue;
    }
}

ด้วย XAML นี้:

<local:CustonTreeView ItemsSource="{Binding TreeRoot}" 
    SelectedCustomThing="{Binding SelectedNode,Mode=TwoWay}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="TreeViewItem">
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
        </Style>
    </TreeView.ItemContainerStyle>
</local:CustonTreeView>

0

ฉันนำโซลูชันมาให้คุณซึ่งมีคุณสมบัติดังต่อไปนี้:

  • รองรับ 2 วิธีที่มีผลผูกพัน

  • อัปเดตคุณสมบัติ TreeViewItem.IsSelected อัตโนมัติ (ตามที่เลือกไว้)

  • ไม่มีคลาสย่อยของ TreeView

  • รายการที่ถูกผูกไว้กับ ViewModel สามารถเป็นประเภทใดก็ได้ (เป็นโมฆะ)

1 / วางรหัสต่อไปนี้ใน CS ของคุณ:

public class BindableSelectedItem
{
    public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.RegisterAttached(
        "SelectedItem", typeof(object), typeof(BindableSelectedItem), new PropertyMetadata(default(object), OnSelectedItemPropertyChangedCallback));

    private static void OnSelectedItemPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var treeView = d as TreeView;
        if (treeView != null)
        {
            BrowseTreeViewItems(treeView, tvi =>
            {
                tvi.IsSelected = tvi.DataContext == e.NewValue;
            });
        }
        else
        {
            throw new Exception("Attached property supports only TreeView");
        }
    }

    public static void SetSelectedItem(DependencyObject element, object value)
    {
        element.SetValue(SelectedItemProperty, value);
    }

    public static object GetSelectedItem(DependencyObject element)
    {
        return element.GetValue(SelectedItemProperty);
    }

    public static void BrowseTreeViewItems(TreeView treeView, Action<TreeViewItem> onBrowsedTreeViewItem)
    {
        var collectionsToVisit = new System.Collections.Generic.List<Tuple<ItemContainerGenerator, ItemCollection>> { new Tuple<ItemContainerGenerator, ItemCollection>(treeView.ItemContainerGenerator, treeView.Items) };
        var collectionIndex = 0;
        while (collectionIndex < collectionsToVisit.Count)
        {
            var itemContainerGenerator = collectionsToVisit[collectionIndex].Item1;
            var itemCollection = collectionsToVisit[collectionIndex].Item2;
            for (var i = 0; i < itemCollection.Count; i++)
            {
                var tvi = itemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
                if (tvi == null)
                {
                    continue;
                }

                if (tvi.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
                {
                    collectionsToVisit.Add(new Tuple<ItemContainerGenerator, ItemCollection>(tvi.ItemContainerGenerator, tvi.Items));
                }

                onBrowsedTreeViewItem(tvi);
            }

            collectionIndex++;
        }
    }

}

2 / ตัวอย่างการใช้งานในไฟล์ XAML ของคุณ

<TreeView myNS:BindableSelectedItem.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />  

0

ฉันเสนอวิธีแก้ปัญหานี้ (ซึ่งฉันพิจารณาว่าการรั่วไหลของหน่วยความจำง่ายที่สุดและฟรี) ซึ่งทำงานได้อย่างสมบูรณ์แบบสำหรับการอัปเดตรายการที่เลือกของ ViewModel จากรายการที่เลือกของมุมมอง

โปรดทราบว่าการเปลี่ยนรายการที่เลือกจาก ViewModel จะไม่อัปเดตรายการที่เลือกของมุมมอง

public class TreeViewEx : TreeView
{
    public static readonly DependencyProperty SelectedItemExProperty = DependencyProperty.Register("SelectedItemEx", typeof(object), typeof(TreeViewEx), new FrameworkPropertyMetadata(default(object))
    {
        BindsTwoWayByDefault = true // Required in order to avoid setting the "BindingMode" from the XAML
    });

    public object SelectedItemEx
    {
        get => GetValue(SelectedItemExProperty);
        set => SetValue(SelectedItemExProperty, value);
    }

    protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
    {
        SelectedItemEx = e.NewValue;
    }
}

การใช้งาน XAML

<l:TreeViewEx ItemsSource="{Binding Path=Items}" SelectedItemEx="{Binding Path=SelectedItem}" >
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.