Binding Datagrid to ObservableCollection<T> in SIlverlight - c#

I am struggling to bind a DataGrid to an ObservableCollection in SIlverlight.
My very simple code is below. It currently shows a blank DataGrid. I have gone through tutorials etc and I am sure I am missing something very very basic.
Main Page XAML
<UserControl xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="Tower.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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot">
<sdk:DataGrid Grid.Row="1" Margin="10" IsReadOnly="True" ColumnWidth="120" ItemsSource="{Binding Path=Tests, Mode=OneWay}" AllowDrop="True" />
</Grid>
Main Page Code Behind:
public partial class MainPage : UserControl
{
public ObservableCollection<Test> Tests { get; set; }
public MainPage()
{
InitializeComponent();
DataContext = this;
Tests = new ObservableCollection<Test>();
Tests.Add(new Test() { Label = "Test1" });
Tests.Add(new Test() { Label = "Test2" });
Tests.Add(new Test() { Label = "Test3" });
Tests.Add(new Test() { Label = "Test4" });
}
}
Test Class:
public class Test : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private String _Label;
public String Label
{
get
{
return _Label;
}
set
{
_Label = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Label"));
}
}
}

There are two issues in your code -
You can only bind with property and not with fields. so make Test property first.
Second, you need to set the DataContext to self for binding to work.
public partial class MainPage : UserControl
{
public ObservableCollection<Test> Tests { get; set; }
public MainPage()
{
InitializeComponent();
DataContext = this;
Tests = new ObservableCollection<Test>();
Tests.Add(new Test() { Label = "Test1" });
Tests.Add(new Test() { Label = "Test2" });
Tests.Add(new Test() { Label = "Test3" });
Tests.Add(new Test() { Label = "Test4" });
}
}
XAML -
<Grid x:Name="LayoutRoot">
<sdk:DataGrid Grid.Row="1" Margin="10" IsReadOnly="True" ColumnWidth="120"
ItemsSource="{Binding DataContext.Tests,
RelativeSource={RelativeSource FindAncestor,
AncestorType= UserControl}}" AllowDrop="True" />
</Grid>
Notice propertyName - It should be Tests and not tests. This is just a side-note, follow the naming conventions of Microsoft. Property name's first letter should be uppercase always.

For binding to work Tests has to be a public property. (I was surprised to see that the property needed to be public but could not get it to work without)
For referenceing the property in the binding you must either
Set the datacontext like RV suggested
Or reference like this:
<Grid>
<DataGrid ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}, Path=Tests}" />
</Grid>

Don't forget :
PUBLIC ObservableCollection tests {get; private set}

Related

WPF Binding and Observable Model fail

In a simple trying-to-learn-WPF experiment I'm trying to bind a property ("InternalName") of an instance of MyModel to the contents of TextBlock "MainWindowTextBlock". Clicking the ``ChangeNameButton" changes the InternalName property of mymodel, but that property change never makes it through to the TextBlock. Nothing happens. What am I doing wrong?
XMAL
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:local="clr-namespace:UserControlExperiments"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel Grid.Row ="0">
<Button Width="100" Height="20" Name="ChangeName" Content="Change the Name" Click="ChangeNameButtonClick"/>
<TextBlock Text=""/>
<TextBlock Name="MainWindowTextBox" Width="100" Height="20" Text="{Binding Path = mymodel.InternalName, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</Window>
CODE BEHIND
public partial class MainWindow : Window
{
public MyModel mymodel;
public MainWindow()
{
InitializeComponent();
DataContext = this.DataContext;
mymodel = new MyModel("The old name");
}
private void ChangeNameButtonClick(object sender, RoutedEventArgs e)
{
mymodel.InternalName = "A new name!";
}
}
public class MyModel : INotifyPropertyChanged
{
private string internalname;
public event PropertyChangedEventHandler PropertyChanged;
public MyModel(string nm)
{
InternalName = nm;
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string InternalName
{
get { return internalname; }
set
{
if (internalname != value)
{
internalname = value;
OnPropertyChanged("InternalName");
}
}
}
}
}
The following markup tries to bind to a property named "mymodel" of the current DataContext of the TextBlock, which is inherited from the parent window:
<TextBlock Name="MainWindowTextBox"
Text="{Binding Path = mymodel.InternalName}"/>
So you need to set the DataContext of the window to itself:
DataContext = this;
And you also need to make mymodel a public property since you cannot bind to fields:
public MyModel mymodel { get; }
Then it should work but you probably also want to change the name of the property to comply with the C# naming standards.
You can also remove Mode=TwoWay from the binding. It makes no sense for a TextBlock.

WPF Sorting an ObservableCollection de-selects a ComboBox

I have a ComboBox where a user can select what JobType they are working on. The ComboBox has a list of AllJobTypes. The problem stems from when a user adds a new JobType, I add the JobType to the AllJobTypes ObservableCollection, then sort it. When the sorting happens the ComboBox get's de-selected and not really sure why. The JobConfig.SelectedJobType.Name never changes in this process. Is there a way to sort an observable collection where it doesn't break the ComboBox?
public class JobTypeList : ObservableCollection<JobType> {
public void SortJobTypes() {
var sortableList = new List<JobType>(this);
sortableList.Sort((x, y) => x.Name.CompareTo(y.Name));
//this works but it creates a bug in the select for JobTypes
for (int i = 0; i < sortableList.Count; i++) {
this.Move(this.IndexOf(sortableList[i]), i);
}
}
And in the XAML
<ComboBox Grid.Column="0" SelectionChanged="JobTypeComboBox_SelectionChanged"
Name="JobTypeComboBox"
ItemsSource="{Binding Path=AllJobTypes}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding
Path=JobConfig.SelectedJobType.Name}" />
Instead of sorting the collection in the view model, you should bind the ComboBox's ItemsSource to a CollectionViewSource, where you can specify a SortDescription:
<Window ...
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
...>
<Window.Resources>
<CollectionViewSource x:Key="cvs" Source="{Binding AllJobTypes}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Name"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
...
<ComboBox ItemsSource="{Binding Source={StaticResource cvs}}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding JobConfig.SelectedJobType.Name}"/>
...
</Window>
For further information see How to: Sort and Group Data Using a View in XAML
Here's a version using ItemsSource/SelectedItem. Note that you can add a new item to the list and sort it without losing the currently selected item in the view.
The window
<Window
x:Class="SortingAList.MainWindow"
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:local="clr-namespace:SortingAList"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox
Text="{Binding NewJobType, Delay=1000}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="200" />
<ComboBox
Grid.Row="1"
ItemsSource="{Binding JobTypes}"
SelectedItem="{Binding SelectedJobType}"
DisplayMemberPath="Name"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="200" />
</Grid>
</Window>
The code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify([CallerMemberName]string property = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public class ViewModel : Notifier
{
private JobType _selectedJobType;
private string _newJobType;
public JobTypeList JobTypes { get; private set; }
public JobType SelectedJobType { get => _selectedJobType; set { _selectedJobType = value; Notify(); } }
public string NewJobType { get => _newJobType; set { _newJobType = value; Notify(); AddNewJobType(value); } }
public ViewModel()
{
JobTypes = new JobTypeList();
JobTypes.Add(new JobType { Name = "Butcher" });
JobTypes.Add(new JobType { Name = "Baker" });
JobTypes.Add(new JobType { Name = "LED maker" });
}
private void AddNewJobType(string name)
{
if(JobTypes.Any(x => x.Name == name)) return;
JobTypes.Add(new JobType { Name = name });
JobTypes.SortJobTypes();
}
}
public class JobType : Notifier
{
private string _name;
public string Name { get => _name; set { _name = value; Notify(); } }
}
Using your JobTypesList
public class JobTypeList : ObservableCollection<JobType>
{
public void SortJobTypes()
{
var sortableList = new List<JobType>(this);
sortableList.Sort((x, y) => x.Name.CompareTo(y.Name));
//this works but it creates a bug in the select for JobTypes
for(int i = 0; i < sortableList.Count; i++)
{
this.Move(this.IndexOf(sortableList[i]), i);
}
}
}

Caliburn.Micro ItemsSource is bound not to current viewmodel but to parent

I'm using Caliburn.Micro for WPF (using VS 2012 and targeting to .NET 4.5.1).
I have problem with binding itemsSource to ComboBox (but I investigate that in my case it happens also with other controls with ItemsSource property, like ListBox).
I have nested views (usercontrols) with viewmodels created with SimpleContainer (IoC).
Here is my problem:
Combobox is populated with items not from its view viewmodel (LanguageSelectionViewModel) but from parent view viewmodel (TopViewModel).
Also, when I removed items collection from parent viewmodel, my combobox was empty.
Code:
MainWindowView.xaml:
<Window
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
d:DataContext="{d:DesignInstance d:Type=mainWindow:MainWindowViewModel}"
>
<Grid>
<top:TopView
HorizontalAlignment="Stretch"
cal:Bind.Model="{Binding TopVM}"
/>
</Grid>
</Window>
MainWindowViewModel:
public class MainWindowViewModel : Screen
{
private TopViewModel topVm;
public TopViewModel TopVM
{
get { return topVm; }
set
{
topVm = value;
NotifyOfPropertyChange(() => TopVM);
}
}
public MainWindowViewModel(TopViewModel topVm, ContentViewModel contentVm)
{
TopVM = topVm;
TopVM.ConductWith(this);
}
}
TopView.xaml:
<UserControl>
<StackPanel Orientation="Horizontal">
<languageSelection:LanguageSelectionView cal:Bind.Model="{Binding LanguageSelectionVM}"/>
</StackPanel>
</UserControl>
TopViewModel.cs:
public class TopViewModel : Screen
{
private LanguageSelectionViewModel _languageSelectionVM;
public LanguageSelectionViewModel LanguageSelectionVM
{
get { return _languageSelectionVM; }
set
{
_languageSelectionVM = value;
NotifyOfPropertyChange(() => LanguageSelectionVM);
}
}
public TopViewModel(ClockViewModel clockVm, LanguageSelectionViewModel languageSelectionVM)
{
this.Items = new ObservableCollection<string>() { "a", "a", "a" };
LanguageSelectionVM = languageSelectionVM;
LanguageSelectionVM.ConductWith(this);
}
private ObservableCollection<string> _items;
public ObservableCollection<string> Items
{
get { return _items; }
set
{
_items = value;
NotifyOfPropertyChange(() => Items);
}
}
}
LanguageSelectionView.xaml:
<UserControl>
<StackPanel Orientation="Vertical">
<ComboBox ItemsSource="{Binding Items}"/>
</StackPanel>
</UserControl>
LanguageSelectionViewModel.cs:
public class LanguageSelectionViewModel : Screen
{
private ObservableCollection<string> _items;
public ObservableCollection<string> Items
{
get { return _items; }
set
{
_items = value;
NotifyOfPropertyChange(() => Items);
}
}
public LanguageSelectionViewModel()
{
this.Items = new ObservableCollection<string>() { "1", "a" };
}
}
I had also tried to populate this collection later, with no success:
protected override void OnViewReady(object view)
{
base.OnViewReady(view);
this.Items = new ObservableCollection<string>() { "1", "a" };
Refresh();
}
DataContext seems to be okay, because binding to textbox
<TextBlock Text="{Binding TestString}"/>
works fine.
Ok, mystery solved.
Instead of nesting controls like this:
<Grid>
<top:TopView
cal:Bind.Model="{Binding TopVM}" />
</Grid>
I should write:
<Grid>
<ContentControl
cal:View.Model="{Binding TopVM}" />
</Grid>
And there is no need to force DataContext.
I figure out that ComboBox whas the only control that had DataContext set to parent View Model, not to proper View model.
It works by forcing it in this way:
<ComboBox
DataContext="{Binding}"
ItemsSource="{Binding Items}" >
But still is the question - why? This is bug or feature of Caliburn.Micro?

C# wpf listbox not updating from ObservableCollection

I'm trying to get the databinding I need to work with a ListBox.
I've parsed some data from a text file to a ObservableCollection<ViewModel> but the data isn't updating in the ListBox.
Here's some information:
The data which is written to from the parser:
class MainData
{
private static ObservableCollection<GroupViewModel> groupModelList = new ObservableCollection<GroupViewModel>();
public static ObservableCollection<GroupViewModel> GroupModelList
{
get { return groupModelList; }
}
}
What GroupViewModel holds (not everything but it's all the same):
class GroupViewModel : INotifyPropertyChanged
{
private GroupModel groupModel;
public event PropertyChangedEventHandler PropertyChanged;
public GroupViewModel()
{
groupModel = new GroupModel();
}
public string Name
{
get { return groupModel.name; }
set
{
if (groupModel.name != value)
{
groupModel.name = value;
InvokePropertyChanged("Name");
}
}
}
...
}
And what GroupModel Holds:
class GroupModel
{
public string name { get; set; }
}
This is how the parser adds new items to the GroupModelView:
if (split[0] == "group")
{
currentGroup = new GroupViewModel();
currentGroup.Name = split[1];
MainData.GroupModelList.Add(currentGroup);
}
I created a ListBox in my WPF application with these XAML options:
<Window x:Class="SoundManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SoundManager.ViewModels"
xmlns:vm2="clr-namespace:SoundManager.Code"
Title="MainWindow" Height="720" Width="1280">
<Window.Resources>
<vm:MainViewModel x:Key="MainViewModel" />
<vm2:MainData x:Key="MainData" />
</Window.Resources>
<ListBox Grid.Row="2" Height="484" HorizontalAlignment="Left" Margin="12,0,0,0" Name="lbFoundItems" VerticalAlignment="Top" Width="201" ItemsSource="{Binding Source={StaticResource MainData}, Path=GroupModelList/Name}" />
but for some reason the data isn't updating in the UI (new items aren't added visibly in the UI).
I've been just getting started with the MVVM pattern and databinding and I can't figure out what I'm doing wrong.
Thanks in advance!
GroupModelList/Name is not a valid property path here. Setting it like that does not make the ListBox show the Name property of the data items in the GroupModelList collection.
You would instead have to set the ListBox's DisplayMemberPath property:
<ListBox ItemsSource="{Binding Source={StaticResource MainData}, Path=GroupModelList}"
DisplayMemberPath="Name"/>
or set the ItemTemplate property:
<ListBox ItemsSource="{Binding Source={StaticResource MainData}, Path=GroupModelList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Moreover, the GroupModelList property should not be static:
class MainData
{
private ObservableCollection<GroupViewModel> groupModelList =
new ObservableCollection<GroupViewModel>();
public ObservableCollection<GroupViewModel> GroupModelList
{
get { return groupModelList; }
}
}
Then you might have MainData as a property in your view model, and bind the ListBox like this:
<ListBox ItemsSource="{Binding Source={StaticResource MainViewModel},
Path=MainData.GroupModelList}" .../>

WPF ComboBox does not display content

I have a simple combo box on my xaml file:
<ComboBox Name="environmentComboBox" Grid.Column="1" Grid.Row="0" Margin="2"
SelectionChanged="environmentComboBox_SelectionChanged"
ItemsSource="{Binding Path=Test}"/>
Here is the code for its content:
private List<string> test = new List<string>(){"1", "2"};
public List<string> Test
{
get
{
return test;
}
set
{
test = value;
}
}
I tried to debug the application, the ComboBox does not show anything.
But when I checked if Test has content, it shows the two strings.
Have to set the views DataContext to the Model/Window containing the List<T>?
If not you need to tell the View what DataContext to use, below is a quick example of a WPF window, and setting the xamls DataContext to the code behind of the View.
Also its recommended to use ObservableCollection<T> when binding collections as adding and removing items will update the ComboBox automatically
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this; // set datacontext
}
private ObservableCollection<string> test = new ObservableCollection<string>() { "1", "2" };
public ObservableCollection<string> Test
{
get { return test; }
set { test = value; }
}
}
<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>
<ComboBox ItemsSource="{Binding Path=Test}"/>
</StackPanel>
</Window>

Categories