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.
Related
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.
How to switch user controls based on treeview Selection Change. I have acheived this on ListBox but couldn't figure out how to do that with Wpf Treeview. Here is my XAML Code.
<Window x:Class="MainScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModelSettings="clr-namespace:ViewModel.Settings" >
<Window.Resources>
<DataTemplate DataType="{x:Type viewModelSettings:BasicSettingsViewModel}">
<viewSettings:BasicSettingsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModelSettings:AdvancedSettingsViewModel}">
<viewSettings:AdvancedSettingsView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<ListBox x:Name="ListBoxMenu"
Grid.Column="0" Margin="5,5,5,385"
ItemsSource="{Binding Settings}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Padding="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Border Grid.Column="1" Margin="5">
<ContentControl Content="{Binding ElementName=ListBoxMenu, Path=SelectedItem}"/>
</Border>
</Grid>
</Window>
I am using Data Template to define Various Viewmodels and binded views with them
To Make it completely MVVM, Here is my code behind
public partial class MainScreen : Window
{
public MainScreen()
{
InitializeComponent();
DataContext = new OptionsDialogViewModel();
}
}
// OptionsDialogViewModel Class
public class OptionsDialogViewModel : ViewModelBase
{
private readonly ObservableCollection<SettingsViewModelBase> _settings;
public ObservableCollection<SettingsViewModelBase> Settings
{
get { return this._settings; }
}
public OptionsDialogViewModel ()
{
_settings = new ObservableCollection<SettingsViewModelBase>();
_settings.Add(new BasicSettingsViewModel());
_settings.Add(new AdvancedSettingsViewModel());
}
}
// SettingsViewModelBase class
public abstract class SettingsViewModelBase : ViewModelBase
{
public abstract string Name { get; }
}
and now my ViewModel(s) are derived from this SettingsViewModelBase
public class AdvancedSettingsViewModel : SettingsViewModelBase
{
public override string Name
{
get { return "Advanced"; }
}
}
I have 2 questions now, Is this the right approach to do this task ?
How can I switch my list view to treeview
Unfortunately selecting items with a treeview is not as straight-forward as selecting with a ListBox.
Unlike simply binding to a ListBox with Content="{Binding ElementName=ListBoxMenu, Path=SelectedItem}", binding to a treeview item involves a bit of code-behind. Check out SO threads here and here for more discussion on the how's and why's.
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.
I posted a similar question earlier, but I was having an issue with getting data from the ViewModel into the View. The issue lies with getting the data out of the object where it is stored when it is time to bind to the View. I created a class that declares 3 items which I use to help populate an ObservableCollection of items that will be bound to a ListBox in the view. I am not sure if I am going about this correctly, so to illustrate I will show below:
ListItem.cs (this is the custom class I defined to help populate the collection of items)
public string Favicon
{
get;
set;
}
public string Name
{
get;
set;
}
public string Address
{
get;
set;
}
MainPage.xaml.cs (here I want to save the data for each item to be added in the ObservableCollection)
void addToFavorites_Click(object sender, EventArgs e)
{
var favoriteItem = new ListItem { Favicon = "/Image/1.jpg", Name = "item1", Address = "some address" };
Settings.FavoritesList.Value.Add(favoriteItem);
}
Settings.cs (the settings class used to store the FavoritesList ObservableCollection)
public class Settings
{
public static Setting<ObservableCollection<ListItem>> FavoritesList = new Setting<ObservableCollection<ListItem>>("Favorites", new ObservableCollection<ListItem>());
}
Now I am attempting to call this stored ObservableCollection FavoritesList in my ViewModel so that I may bind it to a view in another page.
MainViewModel.cs
public ObservableCollection<ListItem> FavoriteItems { get; private set; }
public MainViewModel()
{
FavoriteItems = Settings.FavoritesList.Value;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And then on navigation to my FavoritesPage.xaml, I would like to bind the ViewModel to the View to be displayed in a listbox
FavoritesPage.xaml
<ListBox x:Name="FavoritesListBox" ItemsSource="{Binding FavoriteItems}" SelectionChanged="FavoritesListBox_SelectionChanged">
<StackPanel Orientation="Horizontal" Margin="12,0,12,0">
<Image x:Name="favicon" Source="{Binding Favicon}" Width="50" Height="50"/>
<StackPanel>
<TextBlock x:Name="favoritesName" Text="{Binding Name}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
<TextBlock x:Name="favoritesAddress" Text="{Binding Address}" Margin="12,0,0,0"/>
</StackPanel>
</StackPanel>
</ListBox>
FavoritesPage.xaml.cs
public FavoritesPage()
{
InitializeComponent();
// Set the data context of the listbox control to the sample data
DataContext = App.ViewModel;
}
Now for some reason I cannot set DataContext = App.ViewModel;. I believe I narrowed the problem to when I initially saved the values in the MainPage.xaml.cs using the ListItem class. I am unsure of how to populate the ListPicker from here? Am I doing something wrong somewhere, or should I do something different to set the datacontext correctly?
The setting of the DataContext doesn't look wrong, as long as App.ViewModel is correctly set to an instance of your MainViewModel class.
However, you are defining your ListBox XAML incorrectly.
In order to define how your items will be displayed in a ListBox, you must use the ItemsControl.ItemTemplate property.
<ListBox x:Name="FavoritesListBox" ItemsSource="{Binding FavoriteItems}" SelectionChanged="FavoritesListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="12,0,12,0">
<Image x:Name="favicon" Source="{Binding Favicon}" Width="50" Height="50"/>
<StackPanel>
<TextBlock x:Name="favoritesName" Text="{Binding Name}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
<TextBlock x:Name="favoritesAddress" Text="{Binding Address}" Margin="12,0,0,0"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In App.xaml.cs do:
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
{
viewModel = new MainViewModel();
}
return viewModel;
}
}
In your Xaml do as Daniel recommended:
<ListBox x:Name="FavoritesListBox" ItemsSource="{Binding FavoriteItems}" SelectionChanged="FavoritesListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="12,0,12,0">
<Image x:Name="favicon" Source="{Binding Favicon}" Width="50" Height="50"/>
<StackPanel>
<TextBlock x:Name="favoritesName" Text="{Binding Name}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
<TextBlock x:Name="favoritesAddress" Text="{Binding Address}" Margin="12,0,0,0"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In your MainViewModel.cs do:
public ObservableCollection<ListItem> FavoriteItems
{
get;
private set;
}
Now your DataContext = App.ViewModel should work.
Implement the IS settings like shown here
I have a listbox inside a listbox, and both are binded to an observable collection. I have overloaded the SelectionChanged event for both. For the nested listbox, I have a few labels in its data template. I want to be able to get the content of those labels. It's just difficult because I cannot refer to any of them in the code behind, even with the x:name property defined. Anyone have any ideas?
<ListBox Grid.Row="5" x:Name="lb1" ItemsSource="{Binding}" DataContext="{Binding}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionChanged="lb1_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Label x:Name="txtEnclosure" Content="{Binding Path=EnclosureID}"/>
<......other labels bound to other properties...>
<ListBox x:Name="lbserver" ItemsSource="{Binding Path=Slist}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Label x:Name="txtSlot" Content="{Binding Path=Slot}" />
<Label x:Name="txtServer" Content="{Binding Path=HostnameID}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The parent listbox binds to an observable collection called Elist (an observable collection of Enclosures, a class I defined)
this.DataContext = Settings.Elist;
And the child listbox binds to an observable collection inside of the Enclosure class.
public class Enclosure
{
public ObservableCollection<Server> Slist { get; set; }
...contains other variables as well....
}
In the application, it lists enclosures, and each enclosure has a list of servers. The user can select an Enclosure, and I can get the Enclosure from Elist based on the SelectedIndex (I use ElementAt(SelectedIndex)). Things just get much more tricky when I try to get one of the Servers from the nested listbox. I want to be able to select one of the servers in the list and get the Server from the observable collection Slist. The problem is that when the user selects the server directly, I don't know which Enclosure from Elist the server is from, aaand I can't get the SelectedIndex because I can't refer to anything from the nested listbox in the code behind >.< A very frustrating problem indeed...does anyone have any ideas?
If I can get at the items in the nested listbox in code that would be helpful as well.
Below sample shows how to get selected parent and child when user selects a child, see OnSelectedModelChanged method.
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ListBox ItemsSource="{Binding .}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/>
<ListBox ItemsSource="{Binding Path=Models}" SelectionChanged="OnSelectedModelChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Code behind:
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Collections.Generic;
using System.Windows.Controls.Primitives;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Make> cars = new List<Make>();
cars.Add(new Make("Ford") { Models = new List<Model>() { new Model("F150"), new Model("Taurus"), new Model("Explorer") } });
cars.Add(new Make("Honda") { Models = new List<Model>() { new Model("Accord"), new Model("Pilot"), new Model("Element") } });
DataContext = cars;
}
private void OnSelectedModelChanged(object sender, SelectionChangedEventArgs e)
{
Selector modelSelector = sender as Selector;
Model selectedModel = modelSelector.SelectedItem as Model;
Make selectedMake = modelSelector.DataContext as Make;
}
}
public class Make
{
public Make(string name)
{
Name = name;
}
public string Name { get; private set; }
public IEnumerable<Model> Models { get; set; }
}
public class Model
{
public Model(string name)
{
Name = name;
}
public string Name { get; private set; }
}
}