How to bind ItemsControl of Labels to an ObservableCollection - c#

I have an ItemsControl who has an ItemsSource that is an ObservableCollection. The DataTemplate contains Label controls. My goal is to set the Content property of each of these Labels to the elements in the ObservableCollection but right now, the Content is entirely blank for each of the Label.
It is worth noting that this ItemsControl is nested within another, parent ItemsControl, but let me show:
<ItemsControl ItemsSource={Binding StudentCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
// This is the ItemsControl that is not working properly with the Labels
<ItemsControl ItemsSource="{Binding StudentActivitiesCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Sport, UpdatedSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.Template>
</ItemsControl>
This is my StudentsActivities class:
public class StudentActivities : INotifyPropertyChanged
private string sport;
public string Sport
{
get
{
return this.sport;
}
set
{
this.sport = value;
OnPropertyChanged("Sport");
}
}
}
}
And my working View Model:
private ObservableCollection<StudentActivities> studentActivitiesCollection;
public ObservableCollection<StudentActivities> StudentActivitiesCollection
{
get
{
if (studentActivitiesCollection == null)
studentActivitiesCollection = new ObservableCollection<StudentActivities>();
return studentActivitiesCollection;
}
}
This is the method I am using to populate my ObservableCollection in my ViewModel:
private void PopulateStudentActivitiesCollection(ObservableCollection<Student> Students)
{
foreach (Student s in Students)
{
StudentActivitiesCollection.Add(new StudentActivities () { Sport = StudentSport });
}
}
}

Change
<ItemsControl ItemsSource={StudentCollection}">
to
<ItemsControl ItemsSource={Binding StudentCollection}">
and
<Label Content="{Binding Sport, UpdatedSourceTrigger=PropertyChanged}"/>
to
<Label Content="{Binding Sport}"/>
The last change is not needed but not necessary either.

Related

Two-Way Collection binding

I have a table with checkboxes that is bound to the ObservableCollection > collection, I want to track changes to this collection when one of the checkboxes changes my view.
This is my code:
<UserControl.Resources>
<DataTemplate x:Key="DataTemplate_Level2">
<CheckBox IsChecked="{Binding Path=. ,Mode=TwoWay}" Height="40" Width="50" Margin="4,4,4,4"/>
</DataTemplate>
<DataTemplate x:Key="DataTemplate_Level1">
<ItemsControl x:Name="2st" Items="{Binding Path=. ,Mode=TwoWay}" ItemTemplate="{DynamicResource DataTemplate_Level2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</UserControl.Resources>
<ItemsControl Grid.Column="1" Items="{Binding MyCollection, Mode=TwoWay}" x:Name="lst" ItemTemplate="{DynamicResource DataTemplate_Level1}" Background="Gold"/>
My viewModel property
public ObservableCollection<ObservableCollection<bool>> MyCollection
{
get
{
return someCollection;
}
set
{
someCollection = value;
RaisePropertyChanged(nameof(MyCollection));
}
}
view of table
How Can I pass collection data changes to view model?
You need to declare a new class that will become viewmodel for checkbox with property of type book and proper RaisePropertyChanged invoking. And MyCollection must be collection of collections of instances of that class rather than bool
public class CheckboxViewModel
{
private bool _checkboxValue;
public bool CheckboxValue
{
get
{
return _checkboxValue;
}
set
{
_checkboxValue = value;
RaisePropertyChanged(nameof(CheckboxValue));
}
}
}
Make sure you have two-way binding in checkbox view to that property
BTW - RaisePropertyChanged at setter of MyCollection raises event with wrong property name in you example.

using UserControl as item template for ItemsControl

OK, I think similar questions have already been asked, but I can't get this to work. I have a View with an ItemsControl like this:
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Content="Model Health Report:" Margin="10,10,10,0" Height="26" VerticalAlignment="Top" FontWeight="Bold"/>
<ItemsControl Grid.Row="1"
ItemsSource="{Binding HealthReports, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type hr:HealthReportSummaryControl}"/>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
That has a view model behind it like this:
public class CommunicatorViewModel : ViewModelBase
{
public ObservableCollection<HealthReportSummaryViewModel> HealthReports { get; set; }
public CommunicatorModel Model { get; set; }
public CommunicatorViewModel(HealthReportData data)
{
Model = new CommunicatorModel();
HealthReports = new ObservableCollection<HealthReportSummaryViewModel>
{
new HealthReportSummaryViewModel {Title = "View1", Description = "Some desc."},
new HealthReportSummaryViewModel {Title = "View2", Description = "Some desc."}
};
}
}
As you can see I am binding it to an ObservableCollection of HealthReportSummaryViewModel objects. These are populated in the constructor. I checked the objects at runtime, they are correct.
The actual control looks like this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="2"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0"
Fill="{Binding FillColor}"
Margin="2"/>
<Rectangle Grid.Column="1"
Fill="DarkGray"
Margin="0,2"/>
<Label Content="{Binding Title}"
Grid.Column="2"
Margin="5,0,10,0"
VerticalAlignment="Top"/>
<TextBlock Grid.Column="2"
Margin="5,10,10,0"
TextWrapping="Wrap"
Text="{Binding Description}"/>
</Grid>
With a view model:
public class HealthReportSummaryViewModel : ViewModelBase
{
private System.Windows.Media.Color _fillColor;
public System.Windows.Media.Color FillColor {
get { return _fillColor; }
set { _fillColor = value; RaisePropertyChanged(() => FillColor); }
}
private string _title;
public string Title
{
get { return _title; }
set { _title = value; RaisePropertyChanged(() => Title); }
}
private string _description;
public string Description
{
get { return _description; }
set { _description = value; RaisePropertyChanged(() => Description); }
}
}
I am getting no exceptions, but my window has only empty items. There is a rectangle in the user control that is not dependent on data binding so perhaps this is an issue with the size of the content? I can't figure this out. It's all blank. Do I need to somehow set the size for each ItemsControl item, or will they just adjust to size of the grid they are placed in? What am i missing here? All help will be appreciated.
Your DataTemplate definition is wrong:
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type hr:HealthReportSummaryControl}"/>
</ItemsControl.ItemTemplate>
This defines an empty data template for the items of the HealthReportSummaryControl type.
Instead, you should define it like that:
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type hr:HealthReportSummaryViewModel}">
<hr:HealthReportSummaryControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
This defines a template for the HealthReportSummaryViewModel items.

Dynamically Create Controls in MVVM

I am pretty new to WPF. I am trying to create controls dynamically in MVVM but controls are not rendered on view. I want some number of label and textbox to be created on view.
Below is my code:
Model Code
public class MyModel
{
public string KeyName
{
get;
set;
}
public string KeyValue
{
get;
set;
}
}
ModelView Code
public class MyViewModel
{
private ObservableCollection<MyModel> propertiesList = new ObservableCollection<MyModel>();
public CustomWriterViewModel()
{
GetMyProperties()
}
public ObservableCollection<MyModel> Properties
{
get { return propertiesList; }
}
private void GetMyProperties()
{
MyModel m = new MyModel();
m.KeyName = "Test Key";
m.KeyValue = "Test Value";
MyModel.Add(m);
}
}
View Code(Which is user control)
<Grid>
<ItemsControl ItemsSource="{Binding Properties}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type cw:MyModel}">
<StackPanel Orientation="Horizontal">
<Label Margin="10" Content="{Binding Properties.KeyName}"></Label>
<TextBox Margin="10" Text="{Binding Properties.KeyValue}" Width="250"></TextBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
When view renders, I can only see empty textbox. I cannot understand what is wrong..?
As per my comment:
The DataTemplate receives an individual item as its DataContext, therefore you only need to include item level property names within your binding paths like:
<DataTemplate DataType="{x:Type cw:MyModel}">
<StackPanel Orientation="Horizontal">
<Label Margin="10" Content="{Binding KeyName}"></Label>
<TextBox Margin="10" Text="{Binding KeyValue}" Width="250"></TextBox>
</StackPanel>
</DataTemplate>

Switch User control on Treeview Selection Change

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.

Wrong item selection in ListBox (Windows Phone, Caliburn.Micro, Rx)

I develop an app for Windows Phone 7 with using of Caliburn Micro and Reactive Extensions.
The app has a page with a ListBox control:
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Views:ItemView Margin="0,12,0,0" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I'm using the next ItemView as a DataTemplate:
<UserControl ...>
<Grid x:Name="LayoutRoot"
cal:Message.Attach="[Event Tap] = [Action SelectItem]">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Style="{StaticResource PhoneTextLargeStyle}"
Text="{Binding Name}"
TextWrapping="Wrap" />
<TextBlock Grid.Column="1"
Foreground="{StaticResource PhoneDisabledBrush}"
Style="{StaticResource PhoneTextLargeStyle}"
Text="{Binding Id}" />
</Grid>
</UserControl>
And the corresponding ItemViewModel looks like this:
public class ItemViewModel
{
private readonly INavigationService _navigationService;
public int Id { get; private set; }
public string Name { get; private set; }
public ItemViewModel(Item item)
{
Id = item.Id;
Name = item.Name;
_navigationService = IoC.Get<INavigationService>();
}
public void SelectItem()
{
_navigationService.UriFor<MainViewModel>()
.WithParam(x => x.Id, Id)
.Navigate();
}
}
}
The ListBox populates with items:
public class ListViewModel : Screen
{
private readonly IItemsManager _itemsManager;
private List<ItemViewModel> _items;
public List<ItemViewModel> Items
{
get { return _items; }
private set
{
_items = value;
NotifyOfPropertyChange(() => Items);
}
}
public ListViewModel(IItemsManager itemsManager)
{
_itemsManager = itemsManager;
}
protected override void OnViewReady(object view)
{
base.OnViewReady(view);
Items = null;
var list = new List<ItemViewModel>();
_itemsManager.GetAll()
.SubscribeOn(ThreadPoolScheduler.Instance)
.ObserveOnDispatcher()
.Subscribe((item) => list.Add(new ItemViewModel(item)),
(ex) => Debug.WriteLine("Error: " + ex.Message),
() =>
{
Items = list;
Debug.WriteLine("Completed"));
}
}
}
And here the problems begin.
_itemsManager returns all items correctly. And all items correctly displayed in the ListBox. There is ~150 items.
When I tap on an item then SelectItem method in the corresponding ItemViewModel must be called. And all works fine for first 10-20 items in ListBox. But for all the next items SelectItem method is called in absolutely incorrect ItemViewModel. For example, I tap on item 34 and SelectItem method is called for item 2, I tap 45 - method is called for item 23, and so on. And there is no no dependence between items.
I already head breaks in search of bugs. In what could be the problem?
The solution was found after reading the discussion forum and the page in documentation of Caliburn.Micro.
All problems were because of Caliburn.Micro's Conventions.
To solve the problem I've added to the DataTempalate the next code: cal:View.Model={Binding}. Now part of the page with the ListBox looks like this:
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Views:ItemView Margin="0,12,0,0" cal:View.Model={Binding}/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I think it's not a perfect answer. So I'll be glad if someone can provide better answer and explanation.

Categories

Resources