I'm very new to MVVM and WPF. I'm trying to build a tabcontrol with tabpages presented as usercontrols and i can't find the reason why the usercontrols don't get loaded when i switch between the tabs.
public partial class WndMain : Window
{
public WndMain()
{
InitializeComponent();
var ViewModelWndMain = new ViewModelWndMain();
this.DataContext = ViewModelWndMain;
}
}
Each tabItem has it's own ViewModel:
<Window x:Class="EasyBulking.WndMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:EasyBulking.ViewModels"
xmlns:Tabs="clr-namespace:EasyBulking.GUI"
Title="WndMain" Height="350" Width="525">
<Grid>
<TabControl x:Name="TabsWndMain"
ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTab, Mode=TwoWay}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type ViewModels:ViewModelTabProfile}">
<Tabs:TabProfile />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:ViewModelTabNutrition}">
<Tabs:TabNutrition />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:ViewModelTabTraining}">
<Tabs:TabTraining />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding tabName}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
The corresponding ViewModel:
class ViewModelWndMain : ViewModelBase
{
private ObservableCollection<ViewModelTab> tabs = new ObservableCollection<ViewModelTab>();
private ViewModelTab selectedTab;
private ResourceManager resourceManager { get; set; }
public ViewModelWndMain ()
{
resourceManager = new ResourceManager("EasyBulking.Properties.Resources", Assembly.GetExecutingAssembly());
Tabs.Add(new ViewModelTabProfile(resourceManager.GetString("tabProfile")));
Tabs.Add(new ViewModelTabProfile(resourceManager.GetString("tabNutrition")));
Tabs.Add(new ViewModelTabProfile(resourceManager.GetString("tabTraining")));
SelectedTab = Tabs[0];
}
public ObservableCollection<ViewModelTab> Tabs
{
get
{
return tabs;
}
}
public ViewModelTab SelectedTab
{
get { return selectedTab; }
set {
selectedTab = value;
this.RaisePropertyChangedEvent("SelectedTab");
}
}
}
The ProperyChanged event fires but the UI just doesn't update when i switch the tabs.
abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
OK, just to close this question on a regular answer, here we go:
The code that adds the tabs' viewmodels inserts the same objects for all three tab pages:
Tabs.Add(new ViewModelTabProfile(resourceManager.GetString("tabProfile")));
Tabs.Add(new ViewModelTabProfile(resourceManager.GetString("tabNutrition")));
Tabs.Add(new ViewModelTabProfile(resourceManager.GetString("tabTraining")));
The resource-loaded header text masks that fact and it seems that the pages aren't swapping when in fact they swap from one control to the same type of control again.
Related
I have been struggling with this for a day or so, can't figure out what I'm doing wrong here. I want to be able to select any tab in my observable collection of tabs, and I want my selection to be visible in the UI. I have tried SelectedIndex and SelectedItem. I can see that my Properties are set but my tabs are not selected, nothing happens in the UI. Here is my code:
MainWindow.xaml
<Window x:Class="WpfApplication5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:WpfApplication5"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<ViewModel xmlns="clr-namespace:WpfApplication5" />
</Window.DataContext>
<StackPanel>
<Button Content="Select Tab Index 0" Click="Button_Click_0"/>
<Button Content="Select Tab Index 1" Click="Button_Click_1"/>
<Label Content="{Binding SelectedIndex, UpdateSourceTrigger=PropertyChanged}" />
<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding SelectedIndex, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<uc:TabContent Content="{Binding Content}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_0(object sender, RoutedEventArgs e)
{
var viewModel = (ViewModel)DataContext;
viewModel.SelectedIndex = 0;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
var viewModel = (ViewModel)DataContext;
viewModel.SelectedIndex = 1;
}
}
ViewModel.cs
class ViewModel
{
private int _selectedIndex = 0;
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Tab> _tabCollection = new ObservableCollection<Tab>();
public ViewModel()
{
Tabs.Add(new Tab { Header = "Tab1", Content = new WpfApplication5.TabContent() });
Tabs.Add(new Tab { Header = "Tab2", Content = new WpfApplication5.TabContent() });
}
public ObservableCollection<Tab> Tabs
{
get { return _tabCollection; }
}
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
NotifyPropertyChanged("SelectedIndex");
}
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Tab.cs
class Tab
{
public UserControl Content { get; set; }
public string Header { get; set; }
}
TabContent.xaml
<Grid>
<Label Content="Hello World!" />
</Grid>
Your ViewModel class doesn't implement the INotifyPropertyChanged interface:
class ViewModel : INotifyPropertyChanged
{
...
That's your issue.
I am displaying the data in the gridview in a grouped style. I am already can create new items. Now I want to create a function that can delete the item that I create. Here is my viewmodel :
Viewmodel
public class VM : INotifyPropertyChanged
{
public VM()
{
DeleteItem = new DelegateCommand(DeleteCurrentItem);
}
public ObservableCollection<Contact> ContList = new ObservableCollection<Contact>();
private ObservableCollection<Category> _GroupedCollection;
public ObservableCollection<Category> GroupedCollection
{
get
{
if (_GroupedCollection == null)
_GroupedCollection = new ObservableCollection<Category>();
return _GroupedCollection;
}
set
{
_GroupedCollection = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("GroupedCollection"));
}
}
public void DeleteCurrentItem(object param)
{
var cont= param as Contact;
// there is another class that declare another ObservableCollection that holds all the models.
var category = GroupedCollection.FirstOrDefault(g => g.Key == cont.Account);
if (category != null)
{
if (category.CredCategory.Contains(cont))
{
category.CredCategory.Remove(cont);
}
}
}
public DelegateCommand DeleteItem { get; set; }
private string _Account;
public string Account
{
get { return _Account; }
set
{
_Account = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Account"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
In my XAML, I have a flyout, which work as desired. I can hold the data displayed and the flyout will appear/open. But when I click "Delete", the 'gridview' does not delete it.
View (XAML)
<Page.DataContext>
<data:VM/>
</Page.DataContext>
<Page.Resources>
<CollectionViewSource x:Key="cvs" IsSourceGrouped="True"
Source="{Binding GroupedCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsPath="CredCategory"/>
</Page.Resources>
<Grid>
<FlyoutBase.AttachedFlyout>
<MenuFlyout x:Name="flyout">
<MenuFlyoutItem Text="Delete"
Command="{Binding DataContext.DeleteItem, ElementName=gridview}"
CommandParameter="{Binding}"/>
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<GridView x:Name="gridview"
ItemsSource="{Binding Source={StaticResource cvs}}"
<GridView.ItemTemplate>
<DataTemplate>
. . . .
<DataTemplate/>
<GridView.ItemTemplate>
<GridView/>
<Grid/>
I am showing the code-behind in case someone wants to see it.
View (Code-Behind)
public void cardstack_pass_Holding(object sender, HoldingRoutedEventArgs e)
{
//this is the event declared in the Datatemplate inside gridview
flyout.ShowAt(sender as FrameworkElement);
e.Handled = true;
}
As I stated at the above, my problem is when I click the "Delete" on flyout, it should be deleting the data from the ObservableCollection right? Because as far as I know, the DataContext of the flyout is the DataContext of the data displayed, or am I wrong? How to fix this?
I mean, the gridview's DataContext is the ObservableCollection, and the Stackpanels' DataContext inside gridview's DataTemplate will be the Model Contact right? Since flyout was open at the item created, so the DataContext of flyout will inherit from the item's DataContext, and if the flyout's CommandParameter = "{Binding}", it should pass the Contact inside the item to the viewmodel, isn't it?
I might be missing something here but shouldn't the AttachedFlyout code go in the DataTemplate
Note Binding of Command to element name root (Page name) as we're inside the GridView eg
<Page x:Name="root">
<Page.DataContext>
<data:VM/>
</Page.DataContext>
<Page.Resources>
<CollectionViewSource x:Key="cvs" IsSourceGrouped="True"
Source="{Binding GroupedCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsPath="CredCategory"/>
</Page.Resources>
<Grid>
<GridView x:Name="gridview"
ItemsSource="{Binding Source={StaticResource cvs}}"
<GridView.ItemTemplate>
<DataTemplate>
<FlyoutBase.AttachedFlyout>
<MenuFlyout x:Name="flyout">
<MenuFlyoutItem Text="Delete"
Command="{Binding DataContext.DeleteItem, ElementName=root}"
CommandParameter="{Binding}"/>
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<DataTemplate/>
<GridView.ItemTemplate>
<GridView/>
<Grid/>
This article shows how to use Behaviours which are available in UWP.
I have the following classes / XAML that define my tabs (using SimpleMVVM in case that matters):
Tabs Interface
public interface ITabViewModel
{
String Header { get; set; }
Visibility Visibility { get; set; }
void TabSelected();
}
Tabs VM
public class TabsViewModel : ViewModelBase<TabsViewModel>
{
#region Properties
public ObservableCollection<ITabViewModel> Tabs { get; set; }
public object SelectedTabViewModel
{
get
{
if (this._SelectedTabViewModel == null)
{
_SelectedTabViewModel = Tabs[0];
}
return _SelectedTabViewModel;
}
set
{
if (this._SelectedTabViewModel != value)
{
this._SelectedTabViewModel = value;
NotifyPropertyChanged(m => m.SelectedTabViewModel);
}
}
}
private object _SelectedTabViewModel;
#endregion Properties
#region Constructors
// Default ctor
public TabsViewModel()
{
Tabs = new ObservableCollection<ITabViewModel>();
Tabs.Add((App.Current.Resources["Locator"] as ViewModelLocator).PropertiesViewModel);
Tabs.Add((App.Current.Resources["Locator"] as ViewModelLocator).SystemSetupViewModel);
}
}
Tabs UserControl
<UserControl x:Class="AutomatedSQLMigration.Views.TabsUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:v="clr-namespace:AutomatedSQLMigration.Views"
xmlns:vm="clr-namespace:AutomatedSQLMigration.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
DataContext="{Binding TabsViewModel, Source={StaticResource Locator}}">
<TabControl ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTabViewModel}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:PropertiesViewModel}">
<v:PropertiesUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SystemSetupViewModel}">
<v:SystemSetupUserControl />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="Width" Value="120" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</UserControl>
Properties VM
...
public void TabSelected()
{
Log.Write(LogLevel.Debug, "Selected tab 'Rules'");
}
...
I would like to wire this up such that when the tab is selected, the TabSelected() method is fired for the selected tab. Can someone provide an example of how to do this?
I found another post that mentions this method:
TabItem item = new TabItem();
MyCustomControl mcc = new MyCustomControl();
item.Content = mcc;
Selector.AddSelectedHandler(item, (s,e) =>
{
selectedControl = mcc;
});
but am not sure how I would implement this? Would I apply this to the TabControl VM or each individual user control VM?
How about adding the line in the selected tab setter:
this._SelectedTabViewModel.TabSelected();
1 - I have the TabControl. Its items source is collection of Tabs with
different types. I need to have a different XAML for each type. How to form TabItem header and content depends on ViewModel type?
2 - What is the best solution to encapsulate the XAML for each type of ViewModel? Should I have one UserControl for each type or there is a better solution?
HumanTabViewModel and InvaderTabViewModel are children of BaseViewModel class.
<TabControl ItemsSource="{Binding Tabs}">
</TabControl>
class PanelViewModel : BaseViewModel
{
private readonly ObservableCollection<BaseViewModel> _tabs = new ObservableCollection<BaseViewModel>();
public ObservableCollection<BaseViewModel> Tabs
{
get { return _tabs; }
}
private void InitTabs()
{
// Fill Tabs collection with some logic
var tab1 = new HumanTabViewModel ();
_tabs.Add(tab1);
var tab2 = new InvaderTabViewModel ();
_tabs.Add(tab2);
}
}
With the use of DataTemplates you can define a different looks for your types :
A DataTemplate is used to give a logical entity (.cs) a visual representation , once you assign your logical object (in your case invader/human vm's) as a Content , the framework will traverse up the logical tree looking for a DataTemplate for your type.
if it does not find any , it would just show the "ToString()" of your type.
In your case you have 2 Contents the TabItem.Content , and Header where can be assigned a DataTemplate via HeaderTemplate.
HumanView and InvaderView are UserControls.
CS :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Items.Add(new HumanViewModel());
Items.Add(new InvaderViewModel());
}
private ObservableCollection<BaseViewModel> items;
public ObservableCollection<BaseViewModel> Items
{
get
{
if (items == null)
items = new ObservableCollection<BaseViewModel>();
return items;
}
}
}
public class BaseViewModel : INotifyPropertyChanged
{
public virtual string Header
{
get { return "BaseViewModel"; }
}
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
public class HumanViewModel : BaseViewModel
{
public override string Header
{
get
{
return "HumanViewModel";
}
}
}
public class InvaderViewModel : BaseViewModel
{
public override string Header
{
get
{
return "InvaderViewModel";
}
}
}
XAML :
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:HumanViewModel}">
<local:HumanView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:InvaderViewModel}">
<local:InvaderView />
</DataTemplate>
<Style TargetType="TabItem">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Header,Mode=OneWay}" FontSize="18" FontWeight="Bold" Foreground="DarkBlue" Width="Auto"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding Items, Mode=OneWay}" />
</Grid>
</Window>
I am new to MVVM and still trying to get a grasp on it so let me know if I'm setting this up wrong. What I have is a UserControl with a ListView in it. I populate this ListView with data from the ViewModel then add the control to my MainView. On my MainView I have a button that I want to use to add an item to the ListView. Here is what I have:
Model
public class Item
{
public string Name { get; set; }
public Item(string name)
{
Name = name;
}
}
ViewModel
public class ViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private ObservableCollection<Item> _itemCollection;
public ViewModel()
{
ItemCollection = new ObservableCollection<Item>()
{
new Item("One"),
new Item("Two"),
new Item("Three"),
new Item("Four"),
new Item("Five"),
new Item("Six"),
new Item("Seven")
};
}
public ObservableCollection<Item> ItemCollection
{
get
{
return _itemCollection;
}
set
{
_itemCollection = value;
OnPropertyChanged("ItemCollection");
}
}
}
View (XAML)
<UserControl.Resources>
<DataTemplate x:Key="ItemTemplate">
<StackPanel Orientation="Vertical">
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<UserControl.DataContext>
<local:ViewModel />
</UserControl.DataContext>
<Grid>
<ListView ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{Binding ItemCollection}">
</ListView>
</Grid>
MainWindow
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.mainContentControl.Content = new ListControl();
}
private void Button_Add(object sender, RoutedEventArgs e)
{
}
}
MainWindow (XAML)
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Width="100" Height="30" Content="Add" Click="Button_Add" />
</StackPanel>
<ContentControl x:Name="mainContentControl" />
</DockPanel>
</Grid>
Now, from what I understand, I should be able to just an item to ItemCollection and it will be updated in the view. How do I do this from the Button_Add event?
Again, if I'm doing this all wrong let me know and point me in the right direction. Thanks
You should not interact directly with the controls.
What you need to do is define a Command (a class that implements the ICommand-interface) and define this command on your ViewModel.
Then you bind the Button's command property to this property of the ViewModel. In the ViewModel you can then execute the command and add an item directly to your list (and thus the listview will get updated through the automatic databinding).
This link should provide more information:
http://msdn.microsoft.com/en-us/library/gg405484(v=pandp.40).aspx#sec11