2017-10-09 81 views
1

假設我有3個用戶控件(TIShowNames,TIEnterCode,TIShowFactor)。 他們有自己的看法和他們對應的viewModel。使用MVVm瀏覽TabItem

所有這些3,都在mainwindowView中。

這裏是我的mainwindowView的XAML:

<Controls:TransitionPresenter Name="transContainer" Grid.Row="2" RestDuration="0:0:1" IsLooped="False" Transition="{StaticResource SlideTransition}"> 
     <TabControl Name="TCMain" Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 "> 



      <TabItem Name="TIShowNames" Visibility="Collapsed"> 
       <views:NameView x:Name="NameViewElement" /> 
      </TabItem> 
      <TabItem Name="TIEnterCode" Visibility="Collapsed"> 
       <views:CodeView x:Name="CodeViewElement" /> 
      </TabItem> 
      <TabItem Name="TIShowFactor" Visibility="Collapsed"> 
       <views:FactorDetailView x:Name="FactorDetailViewElement" /> 
      </TabItem> 

     </TabControl> 
    </Controls:TransitionPresenter> 

在我的老的編程風格我用這行代碼通過標籤項目導航(無任何圖案):

private void ChangeTabItemTo(TabItem TI) 
    { 

     transContainer.ApplyTransition("TCMain", "TCMain"); 
     TCMain.SelectedItem = TI; 
    } 

我有「TIShowNames」中的btn節目,所以當我點擊它時,必須轉到「TIShowFactor」。 在MVVM中,ViewModel不知道任何關於視圖的事情(這個item選項卡在它的父視圖中!!!)。所以他如何改變選中的Tab Item而不違反MVVM?

闖闖: 改變的selectedIndex不會因爲這個錯誤的工作:

"System.Windows.Data Error: 40 : BindingExpression path error: 'Index' property not found on 'object' ''MainWindowViewModel' (HashCode=22018304)'. BindingExpression:Path=AAA; DataItem='MainWindowViewModel' (HashCode=22018304); target element is 'TabControl' (Name=''); target property is 'IsSelected' (type 'Boolean')"

更新

控制:TransitionPresenter是從流體DLL

更新

我想嗨德標籤項目的標題,所以沒有人可以通過點擊標題中的標題和navigatoin只能通過在用戶控件btns

回答

2

你可以在視圖定義按次DataTemplate模式類型爲possibe:

<TabControl Name="TCMain" 
      ItemsSource="{Binding ViewModels}" 
      SelectedItem="{Binding ViewModel}" 
      Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 "> 
    <TabControl.ContentTemplate> 
     <DataTemplate> 
      <ContentControl Content="{Binding}"> 
       <ContentControl.Resources> 
        <DataTemplate DataType="{x:Type local:NameViewViewModel}"> 
         <views:NameView /> 
        </DataTemplate> 
        <DataTemplate DataType="{x:Type local:CodeViewViewModel}"> 
         <views:CodeView /> 
        </DataTemplate> 
        <DataTemplate DataType="{x:Type local:FactorDetailViewModel}"> 
         <views:FactorDetailView /> 
        </DataTemplate> 
       </ContentControl.Resources> 
      </ContentControl> 
     </DataTemplate> 
    </TabControl.ContentTemplate> 
</TabControl> 

...和綁定SelectedItem財產來源屬性,您在您的視圖模型設置,如:

public object ViewModel 
{ 
    get { return _vm; } 
    set { _vm = value; NotifyPropertyChanged(); } 
} 
... 
ViewModel = new CodeViewViewModel(); //displays the CodeView 
+0

您應該也許應該提到,這需要將tab view-models添加到主視圖模型的list屬性中,並且該TabControl.ItemsSource綁定到該屬性。以防萬一您的示例代碼不明顯。 –

+0

雖然它是:) – mm8

+0

TabControl.ItemsSource應設置爲ObservableCollection 。什麼可以替代T而不是T?視圖模型不一樣。 – Farshad

2

擴展在MM8的答案,這是我怎麼會做它:

首先,我將創建一個BaseViewModel類,以便由代表TabControl的每個選項卡的每個視圖模型繼承。

我喜歡將它作爲一個抽象類來實現,它具有名爲「Title」的抽象字符串屬性,因此我可以動態創建選項卡並顯示其名稱(或標題)。該類還將實現NotifyPropertyChanged接口。

public abstract class BaseViewModel : INotifyPropertyChanged 
{ 
    public abstract string Title { get; } 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

然後,我會創建從這個基礎視圖模型繼承的每個視圖模型。例如:

public class NameViewModel : BaseViewModel 
{ 
    public override string Title 
    { 
     get 
     { 
      return "Name"; 
     } 
    } 
} 

對於其他視圖模型,您只會更改其中每個視圖模型的「title」屬性。

現在我將創建應用程序的主視圖及其相應的視圖模型。

的MainViewModel將有BaseViewModels的集合和一個「CurrentViewModel」(類型BaseViewModel),希望你想要的所有視圖模型添加到其收藏在它的構造,就像這樣:

public class MainViewModel : BaseViewModel 
{ 
    public override string Title 
    { 
     get 
     { 
      return "Main"; 
     } 
    } 

    private ObservableCollection<BaseViewModel> _viewModels; 
    public ObservableCollection<BaseViewModel> ViewModels 
    { 
     get { return _viewModels; } 
     set 
     { 
      if (value != _viewModels) 
      { 
       _viewModels = value; 
       OnPropertyChanged(); 
      } 
     } 
    } 

    private BaseViewModel _currentViewModel; 
    public BaseViewModel CurrentViewModel 
    { 
     get { return _currentViewModel; } 
     set 
     { 
      if (value != _currentViewModel) 
      { 
       _currentViewModel = value; 
       OnPropertyChanged(); 
      }     
     } 
    } 


    public MainViewModel() 
    { 
     ViewModels = new ObservableCollection<BaseViewModel>(); 
     ViewModels.Add(new NameViewModel()); 
     ViewModels.Add(new CodeViewModel()); 
     ViewModels.Add(new FactorDetailViewModel()); 
    } 
} 

最後,您的主視圖將類似於mm8發佈的內容:

(請注意,我的代碼與mm8代碼的區別在於:(1)您需要將TabControl的DisplayMemberPath設置爲BaseViewModels的「Title」屬性,(2 )您需要將Window的DataContext設置爲MainViewModel)

<Window ...> 
    <Window.DataContext> 
     <local:MainViewModel/> 
    </Window.DataContext> 
    <Grid> 
     <TabControl Name="TCMain" 
      ItemsSource="{Binding ViewModels}" 
      DisplayMemberPath="Title" 
      SelectedItem="{Binding CurrentViewModel}" 
      Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 "> 
      <TabControl.ContentTemplate> 
       <DataTemplate> 
        <ContentControl Content="{Binding}"> 
         <ContentControl.Resources> 
          <DataTemplate DataType="{x:Type local:NameViewModel}"> 
           <local:NameView /> 
          </DataTemplate> 
          <DataTemplate DataType="{x:Type local:CodeViewModel}"> 
           <local:CodeView /> 
          </DataTemplate> 
          <DataTemplate DataType="{x:Type local:FactorDetailViewModel}"> 
           <local:FactorDetailView /> 
          </DataTemplate> 
         </ContentControl.Resources> 
        </ContentControl> 
       </DataTemplate> 
      </TabControl.ContentTemplate> 
     </TabControl> 
    </Grid> 
</Window> 

現在它應該按預期工作。每次更改TabControl的活動選項卡時,控件的SelectedItem屬性都將更改爲相應的視圖模型,該視圖模型將作爲其相應視圖進行模板化。

順便說一下,這種方法被稱爲「View Model First」(而不是View First)。

編輯

如果你想有,有一個命令來改變當前視圖模型視圖模型的一個按鈕,你這是怎麼做到這一點:

我想你是熟悉Josh Smith的RelayCommand。如果你不是,只需在網上搜索它的實現。

您將需要創建您MainViewModel一個ICommand屬性,它負責改變「CurrentViewModel」屬性:

private ICommand _showFactorDetailCommand; 
public ICommand ShowFactorDetailCommand 
{ 
    get 
    { 
     if (_showFactorDetailCommand == null) 
     { 
      _showFactorDetailCommand = new RelayCommand(p => true, p => show()); 
     } 
     return _showFactorDetailCommand; 
    } 
} 

private void show() 
{ 
    CurrentViewModel = ViewModels.Single(s => s.Title == "Factor"); 
} 

上面簡單的show()方法搜索視圖模型的集合,具有標題「因子」並將其設置爲CurrentViewModel,而後者將成爲ContentControl的內容,它充當您的主視圖中TabControl的ContentTemplate。

記住如下,你FactorDetailViewModel應實施:

public class FactorDetailViewModel : ViewModelBase 
{ 
    public override string Title 
    { 
     get 
     { 
      return "Factor"; 
     } 
    } 
} 

你的「NameView」內的按鈕將綁定到這個命令是「MainViewModel」的性質使用的RelativeSource綁定:

<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ShowFactorDetailCommand}" Content="Show Factor" Height="20" Width="60"/> 

您可以使此命令更通用,將您希望導航到的視圖模型的標題作爲命令參數傳遞給:

private ICommand _showCommand; 
public ICommand ShowCommand 
{ 
    get 
    { 
     if (_showCommand == null) 
     { 
      _showCommand = new RelayCommand(p => true, p => show(p)); 
     } 
     return _showCommand; 
    } 
} 

private void show(p) 
{ 
    var vm = (string)p; 
    CurrentViewModel = ViewModels.Single(s => s.Title == vm); 
} 

然後在你的意見,傳遞命令的參數過:

<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ShowCommand}" Content="Show Factor" CommandParameter="Factor" Height="20" Width="60"/> 

最後,完全隱藏你的TabItems,你需要設置你的TabControl的ItemContainerStyle讓您的TabItems的可見性的價值「坍塌」。

<TabControl.ItemContainerStyle> 
    <Style TargetType="{x:Type TabItem}"> 
     <Setter Property="Visibility" Value="Collapsed"/> 
    </Style> 
</TabControl.ItemContainerStyle> 
+0

當你點擊標籤項目標題時,這可能還行。但是這是我的情況: 我在「TIShowNames」中有一個btn「show」,所以當我點擊它時必須轉到「TIShowFactor」。 此btn是在我的用戶控件,並確實知道任何關於tabcntrol或標籤項 – Farshad

+0

好吧。我會編輯我的答案,解釋如何實現這一點。 –