2010-05-05 111 views
3

我創建使用MVVM框架中的WPF應用程序,我已經從約什 - 史密斯對MVVM here...綁定的TabControl的ItemsSource到的ViewModels的一個ObservableCollection導致內容刷新焦點

文章更重要的是通過了幾項功能,我將TabControl綁定到ViewModels的ObservableCollection。這意味着我正在使用一個標籤式MDI接口,它將UserControl顯示爲TabItem的內容。我在應用程序中看到的問題是,當我有多個選項卡並且在選項卡之間來回切換時,每次更改選項卡時都會引用內容。

如果你下載Josh Smith的源代碼,你會發現他的應用程序有同樣的問題。例如,點擊「查看所有客戶」按鈕並向下滾動到ListView的底部。接下來點擊「創建新客戶」按鈕。當您切換回所有客戶視圖時,您會注意到ListView滾動回頂端。如果您切換回新客戶選項卡並將光標置於其中一個文本框中,然後切換到所有客戶選項卡並返回,您會注意到光標現在不見了。

我想這是因爲我使用的是ObservableCollection,但我無法確定。有什麼方法可以防止標籤的內容在收到焦點時刷新?

編輯: 我在我的應用程序上運行profiler時發現我的問題。我定義一個DataTemplate我的ViewModels所以它知道如何渲染視圖模型時,它會顯示在標籤......像這樣:

<DataTemplate DataType="{x:Type vm:CustomerViewModel}"> 
    <vw:CustomerView/> 
</DataTemplate> 

所以每當我切換到不同的選項卡,它必須重新再次創建ViewModel。我通過將我的ViewModels的ObservableCollection更改爲UserControls的ObservableCollection來臨時修復它。但是,如果可能的話,我仍然會喜歡使用DataTemplates。有沒有辦法讓DataTemplate工作?

回答

0

我相信這與綁定模式有關 - 你嘗試{綁定模式= OneWay}嗎? 也綁定到任何'數據源'對象將導致相同的問題。

林不知道如果單向綁定將是你想要的行爲,但我會從那裏開始。

+0

我試過了,但沒有奏效。我已經更新了我的問題,請告訴我是否可以提供任何輸入。謝謝。 – Brent 2010-05-08 15:45:48

0

它與ObservableCollection無關。這是因爲客戶視圖被重用,WPF爲每個客戶創建一個新視圖。

您可能會看到WPF Application Framework (WAF)的Writer示例應用程序。它實現了標籤式MDI界面的TabControl,但不會遇到你在文章中提到的問題。在Writer中通過爲每個文檔「選項卡」創建自己的UserControl來解決該問題。

+0

感謝@jbe,我知道我可以使用UserControls的ObservableCollection解決問題,但如果可能的話,我寧願使用ViewModels的ObservableCollection。我已經更新了我的問題,請告訴我是否可以提供任何輸入。謝謝。 – Brent 2010-05-08 15:49:30

+1

當我開始使用MVVM模式時,我已經使用了WPF的DataTemplate機制,並將View和ViewModel連接在一起。但是我遇到了一些像你在這裏提到的問題。這就是爲什麼我將它們與MEF連接在一起的原因。 WPF只是獲取視圖(UserControls),所以我不再需要這個場景的DataTemplate。 – jbe 2010-05-09 12:23:09

1

WPF的默認行爲是卸載不可見的項目,其中包括卸載不可見的TabItems。這意味着當您返回到選項卡時,TabItem會重新加載,並且任何未綁定的內容(例如滾動位置)將被重置。

有一個很好的網站here它包含代碼來擴展TabControl並阻止它在切換選項卡時破壞它的TabItems,但它現在不再存在。

下面是我用來防止這個問題的代碼。它最初來自該網站,儘管我已對其進行了一些更改。它在切換標籤頁時保留TabItems的ContentPresenter,並在您返回頁面時使用它來重新繪製TabItem。它佔用更多的內存,但是我發現它在性能上更好,因爲TabItem不再需要重新創建其上的所有控件。

// Extended TabControl which saves the displayed item so you don't get the performance hit of 
// unloading and reloading the VisualTree when switching tabs 

// Obtained from http://eric.burke.name/dotnetmania/2009/04/26/22.09.28 
// and made a some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations 

[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))] 
public class TabControlEx : System.Windows.Controls.TabControl 
{ 
    // Holds all items, but only marks the current tab's item as visible 
    private Panel _itemsHolder = null; 

    // Temporaily holds deleted item in case this was a drag/drop operation 
    private object _deletedObject = null; 

    public TabControlEx() 
     : base() 
    { 
     // this is necessary so that we get the initial databound selected item 
     this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; 
    } 

    /// <summary> 
    /// if containers are done, generate the selected item 
    /// </summary> 
    /// <param name="sender"></param> 
    /// <param name="e"></param> 
    void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) 
    { 
     if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) 
     { 
      this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged; 
      UpdateSelectedItem(); 
     } 
    } 

    /// <summary> 
    /// get the ItemsHolder and generate any children 
    /// </summary> 
    public override void OnApplyTemplate() 
    { 
     base.OnApplyTemplate(); 
     _itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel; 
     UpdateSelectedItem(); 
    } 

    /// <summary> 
    /// when the items change we remove any generated panel children and add any new ones as necessary 
    /// </summary> 
    /// <param name="e"></param> 
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 
    { 
     base.OnItemsChanged(e); 

     if (_itemsHolder == null) 
     { 
      return; 
     } 

     switch (e.Action) 
     { 
      case NotifyCollectionChangedAction.Reset: 
       _itemsHolder.Children.Clear(); 

       if (base.Items.Count > 0) 
       { 
        base.SelectedItem = base.Items[0]; 
        UpdateSelectedItem(); 
       } 

       break; 

      case NotifyCollectionChangedAction.Add: 
      case NotifyCollectionChangedAction.Remove: 

       // Search for recently deleted items caused by a Drag/Drop operation 
       if (e.NewItems != null && _deletedObject != null) 
       { 
        foreach (var item in e.NewItems) 
        { 
         if (_deletedObject == item) 
         { 
          // If the new item is the same as the recently deleted one (i.e. a drag/drop event) 
          // then cancel the deletion and reuse the ContentPresenter so it doesn't have to be 
          // redrawn. We do need to link the presenter to the new item though (using the Tag) 
          ContentPresenter cp = FindChildContentPresenter(_deletedObject); 
          if (cp != null) 
          { 
           int index = _itemsHolder.Children.IndexOf(cp); 

           (_itemsHolder.Children[index] as ContentPresenter).Tag = 
            (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item)); 
          } 
          _deletedObject = null; 
         } 
        } 
       } 

       if (e.OldItems != null) 
       { 
        foreach (var item in e.OldItems) 
        { 

         _deletedObject = item; 

         // We want to run this at a slightly later priority in case this 
         // is a drag/drop operation so that we can reuse the template 
         this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind, 
          new Action(delegate() 
         { 
          if (_deletedObject != null) 
          { 
           ContentPresenter cp = FindChildContentPresenter(_deletedObject); 
           if (cp != null) 
           { 
            this._itemsHolder.Children.Remove(cp); 
           } 
          } 
         } 
         )); 
        } 
       } 

       UpdateSelectedItem(); 
       break; 

      case NotifyCollectionChangedAction.Replace: 
       throw new NotImplementedException("Replace not implemented yet"); 
     } 
    } 

    /// <summary> 
    /// update the visible child in the ItemsHolder 
    /// </summary> 
    /// <param name="e"></param> 
    protected override void OnSelectionChanged(SelectionChangedEventArgs e) 
    { 
     base.OnSelectionChanged(e); 
     UpdateSelectedItem(); 
    } 

    /// <summary> 
    /// generate a ContentPresenter for the selected item 
    /// </summary> 
    void UpdateSelectedItem() 
    { 
     if (_itemsHolder == null) 
     { 
      return; 
     } 

     // generate a ContentPresenter if necessary 
     TabItem item = GetSelectedTabItem(); 
     if (item != null) 
     { 
      CreateChildContentPresenter(item); 
     } 

     // show the right child 
     foreach (ContentPresenter child in _itemsHolder.Children) 
     { 
      child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed; 
     } 
    } 

    /// <summary> 
    /// create the child ContentPresenter for the given item (could be data or a TabItem) 
    /// </summary> 
    /// <param name="item"></param> 
    /// <returns></returns> 
    ContentPresenter CreateChildContentPresenter(object item) 
    { 
     if (item == null) 
     { 
      return null; 
     } 

     ContentPresenter cp = FindChildContentPresenter(item); 

     if (cp != null) 
     { 
      return cp; 
     } 

     // the actual child to be added. cp.Tag is a reference to the TabItem 
     cp = new ContentPresenter(); 
     cp.Content = (item is TabItem) ? (item as TabItem).Content : item; 
     cp.ContentTemplate = this.SelectedContentTemplate; 
     cp.ContentTemplateSelector = this.SelectedContentTemplateSelector; 
     cp.ContentStringFormat = this.SelectedContentStringFormat; 
     cp.Visibility = Visibility.Collapsed; 
     cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item)); 
     _itemsHolder.Children.Add(cp); 
     return cp; 
    } 

    /// <summary> 
    /// Find the CP for the given object. data could be a TabItem or a piece of data 
    /// </summary> 
    /// <param name="data"></param> 
    /// <returns></returns> 
    ContentPresenter FindChildContentPresenter(object data) 
    { 
     if (data is TabItem) 
     { 
      data = (data as TabItem).Content; 
     } 

     if (data == null) 
     { 
      return null; 
     } 

     if (_itemsHolder == null) 
     { 
      return null; 
     } 

     foreach (ContentPresenter cp in _itemsHolder.Children) 
     { 
      if (cp.Content == data) 
      { 
       return cp; 
      } 
     } 

     return null; 
    } 

    /// <summary> 
    /// copied from TabControl; wish it were protected in that class instead of private 
    /// </summary> 
    /// <returns></returns> 
    protected TabItem GetSelectedTabItem() 
    { 
     object selectedItem = base.SelectedItem; 
     if (selectedItem == null) 
     { 
      return null; 
     } 

     if (_deletedObject == selectedItem) 
     { 

     } 

     TabItem item = selectedItem as TabItem; 
     if (item == null) 
     { 
      item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem; 
     } 
     return item; 
    } 
} 

的TabControl的模板,我通常使用看起來像這樣:

<Style x:Key="TabControlEx_NoHeadersStyle" TargetType="{x:Type local:TabControlEx}"> 
    <Setter Property="SnapsToDevicePixels" Value="true"/> 
    <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="{x:Type localControls:TabControlEx}"> 
       <DockPanel> 
        <!-- This is needed to draw TabControls with Bound items --> 
        <StackPanel IsItemsHost="True" Height="0" Width="0" /> 
        <Grid x:Name="PART_ItemsHolder" /> 
       </DockPanel> 
      </ControlTemplate> 
     </Setter.Value> 
    </Setter> 
</Style> 
相關問題