WinRT MVVM-Light ComboBox in ListView with different ItemsSources - c#

I'm working on a WinRT application where i have a Listview with a ComboBox.
The Listview has a particular ObservableCollection as Itemssource, The ComboBox Should have another ObservableCollection as ItemsSource because i should be able to dynamicaly change the contents of the ComboBox.
I'm using the MVVM-Light framework, The ObservableCollections are filled in the ViewModel and displayed through databinding.
I'll give you an example Xaml code:
<Page x:Class="MvvmLight2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ignore="http://www.ignore.com"
mc:Ignorable="d ignore"
d:DesignHeight="768"
d:DesignWidth="1366"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Page.Resources>
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{Binding CollectionOne}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding StringOne}"></TextBlock>
<ComboBox ItemsSource="{Binding CollectionTwo}" Width="500">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding StringTwo}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
And Corresponding ViewModel:
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
CollectionOne = new ObservableCollection<ClassOne>();
for (int i = 0; i < 4; i++)
{
var temp = new ClassOne()
{
StringOne = "String " + i.ToString()
};
CollectionOne.Add(temp);
}
CollectionTwo = new ObservableCollection<ClassTwo>();
CollectionTwo.Add(new ClassTwo("ADV"));
CollectionTwo.Add(new ClassTwo("Wettelijk"));
}
private ObservableCollection<ClassOne> _collectionOne;
public ObservableCollection<ClassOne> CollectionOne
{
get { return _collectionOne; }
set
{
if (_collectionOne == value)
{
return;
}
_collectionOne = value;
RaisePropertyChanged(() => CollectionOne);
}
}
private ObservableCollection<ClassTwo> _collectionTwo;
public ObservableCollection<ClassTwo> CollectionTwo
{
get { return _collectionTwo; }
set
{
if (_collectionTwo == value)
{
return;
}
_collectionTwo = value;
RaisePropertyChanged(() => CollectionTwo);
}
}
}
In ClassOne and ClassTwo are for the example just one property in each class with a string.
Both collections have to remain seperate because they can be different in length when randomly filled.
EDIT
#Josh I followed your instructions but it still doesn't seem to work, Here are my adjustments:
<Page x:Class="MvvmLight2.MainPage"
x:Name="MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ignore="http://www.ignore.com"
mc:Ignorable="d ignore"
d:DesignHeight="768"
d:DesignWidth="1366"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Page.Resources>
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{Binding CollectionOne}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding StringOne}"></TextBlock>
<ComboBox ItemsSource="{Binding ElementName=MyControl, Path=CollectionTwo}" Width="500">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding StringTwo}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>

Since you are using the ViewModel Locator to set your datacontext you can reuse this to find the property CollectionTwo.
Your binding would look like this:
<ComboBox ItemsSource="{Binding Path=Main.CollectionTwo, Source={StaticResource Locator}}" />

You need to move up one level in the datacontext to search the view model instead of the item that is bound at the list view level using RelativeSource:
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Page}}, Path=CollectionTwo}" />
and for WinRT situations, use a control name:
ElementName=MyControl
instead of searching by AncestorType and give the page a name of 'MyControl'. It would then look like
<ComboBox ItemsSource="{Binding ElementName=MyControl, Path=DataContext.CollectionTwo}" />
and your Page would look like
<Page x:Name="MyControl"

The ComboBox binding is relative to the Binding of the ListItem. So it searches for CollectionTwo as a property of ClassOne. Either look at the RelativeSource to bind to or move the CollectionTwo to class ClassOne. That way, you can easily build up different lists for each ListViewItem.

Related

WPF binding different UserControls in a DataTemplate of TabControl

As a new in WPF and MVVM light, I am struggling to apply the MVVM pattern in a TabControl. I will give you an example of what I am trying to achieve.
TabOne xaml and its view model
<UserControl x:Class="TestTabControl.TabOne"
xmlns:local="clr-namespace:TestTabControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="tab one ..." FontWeight="Bold" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</UserControl>
//TabOne ViewModel
class TabOne : ViewModelBase
{
public string TabName
{
get
{
return "TabOne";
}
}
}
TabTwo xaml and its viewmodel
<UserControl x:Class="TestTabControl.TabTwo"
xmlns:local="clr-namespace:TestTabControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="tab two ..." FontWeight="Bold" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</UserControl>
//TabTwo ViewModel
class TabTwo : ViewModelBase
{
public string TabName
{
get
{
return "TabTwo";
}
}
}
and finally the MainWindow xaml and its viewmodel
<Window x:Class="TestTabControl.MainWindow"
xmlns:local="clr-namespace:TestTabControl"
mc:Ignorable="d"
Title="Test Tab Control" MinWidth="500" Width="1000" Height="800">
<TabControl ItemsSource="{Binding TabViewModels}" >
<TabControl.ItemTemplate >
<!-- header template -->
<DataTemplate>
<TextBlock Text="{Binding TabName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
?????????
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
//MainWindow ViewModel
class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<ViewModelBase> _tabViewModels;
public MainWindowViewModel()
{
_tabViewModels = new ObservableCollection<ViewModelBase>();
TabViewModels.Add(new TabOne());
TabViewModels.Add(new TabTwo());
}
public ObservableCollection<ViewModelBase> TabViewModels
{
get
{
return _tabViewModels;
}
set // is that part right?
{
_tabViewModels = value;
RaisePropertyChanged(() => TabViewModels);
}
}
}
What am I supposed to write in the DataTemplate? Can I pass both usercontrols for TabOne and TabTwo in this DataTemplate in order to get the view for each tab I click? Or do I need to write another DataTemplate?
You may already knew the answer by now. But for the benefits of other people, what you need to do is:
<Grid Margin="10">
<Grid.Resources>
<DataTemplate DataType="{x:Type local:TabOne}">
<local:UserControlOne/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:TabTwo}">
<local:UserControlTwo/>
</DataTemplate>
</Grid.Resources>
<TabControl Margin="10"
ItemsSource="{Binding TabViewModels}">
</TabControl>
</Grid>
Please note that, your UserControl for TabOne ViewModel is also named TabOne.
I changed it to UserControlOne. Same applies to UserControlTwo.

WPF implicit datatemplate not displaying view

fellow programmers.
I'm trying to build my first WPF app with tabbed interface. I've decided to not rely on any MVVM framework, since I'm only starting my journey.
I've found quite a few tutorials, but can't make WPF to pick up correct view for viewmodels.
What I have is Shell view, with underlying DataContext with ObservableCollection of my "Tabs viewmodels":
Shell.Xaml
<Controls:MetroWindow x:Class="WpfApplication1.View.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Shell" Height="327" Width="667"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:v="clr-namespace:WpfApplication1.View"
xmlns:vm="clr-namespace:WpfApplication1.ViewModel"
>
<Grid>
<TabControl ItemsSource="{Binding Pages}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<DataTemplate DataType="x:Type vm:FirstPageModel">
<v:FirstPageView />
</DataTemplate>
<DataTemplate DataType="x:Type vm:SecondPageModel">
<v:SecondPageView />
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Grid>
All I with - to get my views inside tab's of tabcontrol. But, no matter where I put my DataTemplate (window.resources, tabcontrol.resources, etc), I only get something like this:
As I can understand - somehow WPF doesn't see my views, but I don't understand why.
My "views" are simple UserControls, like this:
<UserControl x:Class="WpfApplication1.View.FirstPageView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="{Binding Message}" Margin="36,131,47,127" />
</Grid>
and corresponding ViewModel is:
public class FirstPageModel:BaseViewModel
{
public override string DisplayName
{
get
{
return "First page";
}
}
public string Message { get { return "Hi from " + DisplayName; } }
}
Could somebody tell me what's wrong with my code?
Edit:
ShellViewModel:
public class ShellViewModel : BaseViewModel
{
public override string DisplayName
{
get
{
return "First";
}
}
private ObservableCollection<BaseViewModel> pages;
public ObservableCollection<BaseViewModel> Pages
{
get{
if(this.pages==null)
{
this.pages = new ObservableCollection<BaseViewModel>{
new FirstPageModel(),
new SecondPageModel()
};
}
return this.pages;
}
}
}
App.xaml.cs:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Shell sh = new Shell();
ShellViewModel shvm = new ShellViewModel();
sh.DataContext = shvm;
sh.Show();
}
}
The DataTemplate definition is incorrect. You must use curly braces for x:Type markup extension with DataType attribute value:
<TabControl ItemsSource="{Binding Pages}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<!-- Use curly braces for x:Type markup extension -->
<DataTemplate DataType="{x:Type vm:FirstPageModel}">
<v:FirstPageView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SecondPageModel}">
<v:SecondPageView />
</DataTemplate>
</TabControl.Resources>
</TabControl>

How to use binding in a TabItem with DataTemplate in WPF?

I'm trying to do something basic (I think!), but I've got a problem. I have got a TabControl and 3 TabItems. The 2 first items must be untouched, and I want to apply a dataTemplate on the third tabItem, so I used a DataTemplateSelector. This is OK, it works. But then, I want to fill data in the third tabItem with my datamodel. Binding is not working because my DataContext is always "null" in the tabItem. How can I set the DataContext in the tabItem created by the dataTemplate ? Here is my code :
XAML :
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="910" Width="1200">
<Window.Resources>
<DataTemplate x:Key="configurationTemplate" DataType="{x:Type local:ConfigurationDatamodel}">
<local:ConfigurationTemplateUC DataContext="{Binding DataContext.ConfigurationDatamodel}" />
</DataTemplate>
<local:TabItemTemplateSelector ConfigurationTemplate="{StaticResource configurationTemplate}" x:Key="tabItemTemplateSelector"/>
</Window.Resources>
<Grid>
<TabControl Height="800" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="tabControl1" VerticalAlignment="Top" ContentTemplateSelector="{StaticResource tabItemTemplateSelector}">
<TabItem Header="TabItem1" Name="tabItem1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<some stuff />
</TabItem>
<TabItem Header="TabItem2" Name="tabItem2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<some stuff />
</TabItem>
<TabItem Header="TabItem3" Name="tabItem3" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
</TabItem>
</TabControl>
</Grid>
</Window>
UserControl of my DataTemplate :
<UserControl x:Class="WpfApplication1.ConfigurationTemplateUC"
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"
mc:Ignorable="d"
d:DesignHeight="800" d:DesignWidth="1000">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Label Content="File name : " Height="28" HorizontalAlignment="Left" Margin="50,43,0,0" Name="label1" VerticalAlignment="Top" FontSize="16"/>
<Label Content="{Binding Path=File}" FontSize="16" Height="28" HorizontalAlignment="Left" Margin="160,43,0,0" Name="label2" VerticalAlignment="Top" Width="324" />
</Grid>
</UserControl>
Datamodel :
public class ConfigurationDatamodel
{
private string file;
public string File
{
get { return this.file; }
set
{
this.file= value;
}
}
public ConfigurationDatamodel()
{}
public ConfigurationDatamodel(string file)
{
this.file= file;
}
}
Code-behind :
public MainWindow()
{
ConfigurationDatamodel dt1 = new ConfigurationDatamodel("example.txt");
InitializeComponent();
tabItem3.DataContext = dt1;
}
There is no binding errors in the console, but the label containing the filename is always empty. The DataContext of the UserControl "ConfigurationTemplateUC" is always "null".
Any thoughts?
EDIT
If I set the DataContext in the contructor of the UserControl, it works :
public ConfigurationTemplateUC()
{
InitializeComponent();
ConfigurationDatamodel dt1 = new ConfigurationDatamodel("example.txt");
this.DataContext = dt1;
}
How can I set this dataContext with the DataTemplate ?
In your DataTemplate, Remove the DataContext binding from
<local:ConfigurationTemplateUC DataContext="{Binding DataContext.ConfigurationDatamodel}" />
...leaving only
<local:ConfigurationTemplateUC />
UI in a DataTemplate automatically gets the bound data as its DataContext, so in your case you override the correct DataTemplate with a path to something that can't be found.
EDIT:
Also, you don't need a DataTemplateSelector to do this. You can remove the selector and instead simply use TabItem.ContentTemplate:
<TabItem Header="TabItem3" Name="tabItem3" ContentTemplate="{StaticResource configurationTemplate}" >
...

WPF: Can't access the view. Datagrid is not refreshing with data in tabs

I'm following this How to create tab-able content in WPF/C#? but I want each tab to show a datagrid. The datagrid doesn't show and also doesn't show the data. When I step into the code, I do see 0,1 being set.
MainWindow.xaml
<Window x:Class="MVVMDataInstances.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:viewModel="clr-namespace:MVVMDataInstances"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModel:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ContentTemplate"
DataType="{x:Type viewModel:ChildViewModel}"/>
</Grid.Resources>
<TabControl ContentTemplate="{StaticResource ContentTemplate}"
ItemsSource="{Binding Items}" />
</Grid>
</Window>
MainWindowViewModel.cs
public ObservableCollection<ChildViewModel> Items { get; private set; }
public MainWindowViewModel()
{
Items = new ObservableCollection<ChildViewModel> {new ChildViewModel(0), new ChildViewModel(1)};
}
ChildView.xaml
<UserControl x:Class="MVVMDataInstances.View"
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:ViewModel="clr-namespace:MVVMDataInstances"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<ViewModel:ChildViewModel/>
</UserControl.DataContext>
<DataGrid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Grid, Mode=TwoWay}" />
</UserControl>
ChildViewModel.cs
public class ChildViewModel : ViewModelBase
{
private ObservableCollection<ChildModel> _grid;
public ObservableCollection<ChildModel> Grid
{
get { return _grid; }
private set
{
_grid = value;
OnPropertyChanged("Grid");
}
}
public ChildModel Data { get; set; }
public ChildViewModel()
{
}
public ChildViewModel(int tabNumber)
{
Data = new ChildModel {A = tabNumber.ToString(CultureInfo.InvariantCulture)};
Grid = new ObservableCollection<ChildModel> {Data};
}
}
ChildModel.cs
public class ChildModel
{
public string A { get; set; }
public string B { get; set; }
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I would like to see one grid per tab. The entry on the first tab has a value of 0 for property A. The entry of the second tab has a value of 1 for the property B.
I see that when OnPropertyChanged is called PropertyChanged is null.
I can access the datagrid if I have this in MainWindow.xaml
<Grid>
<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabTitle}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<DataGrid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" AutoGenerateColumns="True"
ItemsSource="{Binding Grid}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
But OnPropertyChanged is always null for this and I don't see the grid
<DataTemplate x:Key="ContentTemplate"
DataType="{x:Type viewModel:ChildViewModel}">
<viewModel:ChildView />
</DataTemplate>
Your DataTemplate is empty. From your question, you probably want to do something along these lines:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ContentTemplate" TargetType="{x:Type viewModel:ChildViewModel}">
<DataGrid AutoGenerateColumns="False" . . .>
<DataGrid.Columns>
<!-- Your column definitions here -->
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</Grid.Resources>
<TabControl ContentTemplate="{StaticResource ContentTemplate}"
ItemsSource="{Binding Items}" />
</Grid>
This DataTemplate tells the ChildViewModel.cs that it's appearance is View.xaml :
<DataTemplate x:Key="ContentTemplate"
DataType="{x:Type viewModel:ChildViewModel}">
<viewModel:View />
</DataTemplate>
As a result behind the scenes it also sets each View's DataContext to an instance of ChildViewModel.
I follow this MVVM: ViewModel inheritance to have ViewModel Inheritance. The reason PropertyChanged was null because the constructor was initialized twice. First time with an integer, and then second time with the InitializedComponent. If I comment out the InitializedComponent, the grid was never initialized. The answer to the other stackoverflow question makes everything cleaner with view model inheritance.

Binding ObervableCollection to ListBox

I have the following code that should display some information about ContactLists in a ListBox but there seems to be a problem with the binding as nothing is displayed. What am I missing? Would appreciate any help. Thanks!
XAML
</Window>
<Window.Resources>
<DataTemplate x:Key="ContactsTemplate">
<WrapPanel>
<TextBlock TextWrapping="Wrap"
Text="{Binding ContactListName, Mode=Default}"/>
</WrapPanel>
</DataTemplate>
</Window.Resources>
<Grid x:Name="LayoutRoot"
Background="#FFCBD5E6">
<Grid.DataContext>
<local:MyViewModel/>
</Grid.DataContext>
<ListBox x:Name="contactsList"
SelectionMode="Extended"
Margin="7,8,0,35"
ItemsSource="{Binding ContactLists}"
ItemTemplate="{DynamicResource ContactsTemplate}"
HorizontalAlignment="Left"
Width="178"
SelectionChanged="contactsList_SelectionChanged"/>
</Grid>
</Window>
ViewModel
public class MyViewModel
{
public ObservableCollection<ContactListModel> ContactLists;
public MyViewModel()
{
var data = new ContactListDataAccess();
ContactLists = data.GetContacts();
}
}
Change ContactLists to be a property for the binding to work correctly:
public class MyViewModel
{
public ObservableCollection<ContactListModel> ContactLists{get;set;}
public MyViewModel()
{
var data = new ContactListDataAccess();
ContactLists = data.GetContacts();
}
}
See here for more info.

Categories

Resources