Listbox doesn't show the contents of an observablecollection - wpf

I have made an application with the mvvm model and a sql database connected to azure. When I try to get information in the database my listbox detects how many objects are in the collection and shows the path to the List instead of the content.
I have tried my sql query and this just gives back the data fine.
view:
xmlns:local="clr-namespace:Lance_Theunis_r0702301_2ITFA"
xmlns:viewmodel="clr-
namespace:Lance_Theunis_r0702301_2ITFA.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<viewmodel:WagenViewModel x:Key="WagenViewModel" />
</Window.Resources>
<DockPanel LastChildFill="True" DataContext="{StaticResource
WagenViewModel}">
<StackPanel DockPanel.Dock="Left" Width="333px">
<ListBox ItemsSource="{Binding Path=Wagens}" />
</StackPanel>
viewmodel:
namespace Lance_Theunis_r0702301_2ITFA.ViewModel
{
class WagenViewModel : BaseViewModel
{
public WagenViewModel()
{
LeesWagens();
}
private ObservableCollection<Wagen> wagens;
public ObservableCollection<Wagen> Wagens
{
get
{
return wagens;
}
set
{
wagens = value;
NotifyPropertyChanged();
}
}
private void LeesWagens()
{
DataService wagenDS = new DataService();
wagens = new ObservableCollection<Wagen>(wagenDS.GetWagens());
}
}
}
DataService class:
namespace Lance_Theunis_r0702301_2ITFA.Model
{
class DataService
{
private static string connectionString =
ConfigurationManager.ConnectionStrings["azure"].ConnectionString;
private static IDbConnection db = new SqlConnection(connectionString);
public List<Wagen> GetWagens()
{
string sql = "Select naam from Wagen order by naam";
return (List<Wagen>)db.Query<Wagen>(sql);
}
}
}
There are no error messages.
The listbox shows (Lance_Theunis_r0702301_2ITFA.Model.Wagen) instead of for example (bmw m3).

Set the DisplayMemberPath property to "naam" or whatever the name of the property of the Wagen class that you want to display is:
<ListBox ItemsSource="{Binding Path=Wagens}" DisplayMemberPath="naam" />

Related

Get the number of items currently displayed in the listview WPF MVVM

I have a listview that can be filtered using a textbox:
<TextBox TextChanged="txtFilter_TextChanged" Name="FilterLv"/>
In the view code-behind I do the following:
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(this.lv.ItemsSource);
view.Filter = UserFilter;
private bool UserFilter(object item)
{
if (String.IsNullOrEmpty(FilterLv.Text))
return true;
else
{
DataModel m = (item as DataModel);
bool result = (m.Name.IndexOf(Filter.Text, StringComparison.OrdinalIgnoreCase) >= 0 ||
//m.Surname.IndexOf(Filter.Text, StringComparison.OrdinalIgnoreCase) >= 0);
return result;
}
}
private void Filter_TextChanged(object sender, TextChangedEventArgs e)
{
CollectionViewSource.GetDefaultView(this.lv.ItemsSource).Refresh();
}
Now I have placed a label in the view and I would like this label to show the number of items currently displayed in the listview.
How can I do it? I have found things like this but I don't understand at all what is RowViewModelsCollectionView. In this link it is suggested to bind as below:
<Label Content="{Binding ModelView.RowViewModelsCollectionView.Count}"/>
Could anyone explain me or provide a very little and simple example on how to do it?
FINAL UPDATE:
View model:
public class TestViewModel
{
// lv is populated later in code
public ObservableCollection<DataModel> lv = new ObservableCollection<DataModel>();
public ObservableCollection<DataModel> LV
{
get
{
return this.lv;
}
private set
{
this.lv= value;
OnPropertyChanged("LV");
}
}
private CollectionView view;
public TestViewModel()
{
this.view = (CollectionView)CollectionViewSource.GetDefaultView(this.LV);
view.Filter = UserFilter;
}
private string textFilter;
public string TextFilter
{
get
{
return this.textFilter;
}
set
{
this.textFilter= value;
OnPropertyChanged("TextFilter");
if (String.IsNullOrEmpty(value))
this.view.Filter = null;
else
this.view.Filter = UserFilter;
}
}
private bool UserFilter(object item)
{
if (String.IsNullOrEmpty(this.TextFilter))
return true;
else
{
DataModel m = (item as DataModel);
bool result = (m.Name.IndexOf(this.TextFilter, StringComparison.OrdinalIgnoreCase) >= 0 ||
//m.Surname.IndexOf(this.TextFilter, StringComparison.OrdinalIgnoreCase) >= 0);
return result;
}
}
/// <summary>
/// Número de registros en la listview.
/// </summary>
public int NumberOfRecords
{
get
{
return this.view.Count;
}
}
}
View (xaml):
<!-- search textbox - filter -->
<TextBox TextChanged="txtFilter_TextChanged"
Text="{Binding TextFilter, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<!-- label to show the number of records -->
<Label Content="{Binding NumberOfRecords}"/>
view code-behind (xaml.cs):
private void txtFilter_TextChanged(object sender, TextChangedEventArgs e)
{
CollectionViewSource.GetDefaultView((DataContext as TestViewModel).LV).Refresh();
}
It is filtering ok when I type in the search textbox and listview is updated correctly but the number of records is always 0.
What am i doing wrong?
ATTEMPT2:
Below another attempt not working. If I attach my listivew to the View declared in model view then no items are shown. If I attach listview to LV in model view then items are shown, and when I filter through my search textbox it filters ok, listview is updated but the number of rows shown in the listview always remains to 0.
Notes:
I am using NET 3.5 Visual Studio 2008.
I need to set View as writable in model view because I do not set it
in view model constructor, instead i set it in LoadData method in
view model. LoadData is called from view code-behind constructor.
View Model:
namespace MyTest.Example
{
public Class TestViewModel : INotifyPropertyChanged // Implementations not here to simplify the code here.
{
private ObservableCollection<DataModel> lv;
public ObservableCollection<DataModel> LV
{
get
{
return this.lv;
}
private set
{
this.lv = value;
OnPropertyChanged("LV");
}
}
public CollectionView View { get; set; }
public TestViewModel()
{
this.LV = new ObservableCollection<DataModel>();
// this.View = (CollectionView)CollectionViewSource.GetDefaultView(this.LV);
// this.View.Filter = UserFilter;
}
private string textFilter = string.Empty;
public string TextFilter
{
get
{
return this.textFilter ;
}
set
{
this.textFilter = value;
OnPropertyChanged("TextFilter");
this.View.Refresh();
}
}
private bool UserFilter(object item)
{
if (String.IsNullOrEmpty(this.TextFilter))
return true;
else
{
DataModel m = (item as DataModel);
bool result = (m.Name.IndexOf(this.TextFilter, StringComparison.OrdinalIgnoreCase) >= 0 ||
//m.Surname.IndexOf(this.TextFilter, StringComparison.OrdinalIgnoreCase) >= 0);
return result;
}
}
public void LoadData()
{
this.LV = LoadDataFromDB();
this.View = (CollectionView)CollectionViewSource.GetDefaultView(this.LV);
this.View.Filter = UserFilter;
}
} // End Class
} // End namespace
View code-behing (xaml.cs):
namespace MyTest.Example
{
public Class TestView
{
public TestView()
{
InitializeComponent();
(DataContext as TestViewModel).LoadData();
}
}
}
View (xaml):
xmlns:vm="clr-namespace:MyTest.Example"
<!-- search textbox - filter -->
<TextBox Text="{Binding Path=TextFilter, UpdateSourceTrigger=PropertyChanged}">
<!-- label to show the number of records -->
<Label Content="{Binding Path=View.Count}" ContentStringFormat="No. Results: {0}"/>
<ListView Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Path=View}" SelectionMode="Extended" AlternationCount="2">
ATTEMPT 3:
Finally I have get it to work. Solution is the same as ATTEMPT2 but making below changes:
I have replaced this:
public CollectionView View { get; set; }
by this one:
private CollectionView view;
public CollectionView View {
get
{
return this.view;
}
private set
{
if (this.view == value)
{
return;
}
this.view = value;
OnPropertyChanged("View");
}
}
All the rest remains the same as in ATTEMPT2. In view View.Count and assigning View as ItemsSource to my listview now is working all perfectly.
You should use
<Label Content="{Binding ModelView.Count}"/>
instead of
<Label Content="{Binding ModelView.RowViewModelsCollectionView.Count}"/>
RowViewModelsCollectionView in the other question is the same as ModelView is in your case.
Edit
Count is a property from the CollectionView
For further information have a look at the MSDN
Edit 2
When you dont want to do it via XAML like in my example you have to implement INotifyPropertyChanged and raise this whenever the bound property is changed because otherwiese the UI won't get the change.
In your case: you have to call OnPropertyChanged("NumberOfRecords"); in your filter method. But it would be easier to do it via xaml like i Wrote earlier.
Here is a fully working example with the CollectionView in the view model, and the filter count automatically flowing to the bound control. It uses my mvvm library for the base ViewModel class to supply INotifyPropertyChanged, but you should easily be able to substitute your own system, I'm not doing anything special with it.
The full source code can be downloaded from here
XAML:
<Window
x:Class="FilterWithBindableCount.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:local="clr-namespace:FilterWithBindableCount"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
d:DataContext="{d:DesignInstance local:MainWindowVm}"
mc:Ignorable="d">
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label
Grid.Row="0"
Grid.Column="0"
Margin="4">
Filter:
</Label>
<TextBox
Grid.Row="0"
Grid.Column="1"
Margin="4"
VerticalAlignment="Center"
Text="{Binding Path=FilterText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="4"
Text="{Binding Path=PeopleView.Count, StringFormat={}Count: {0}}" />
<DataGrid
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="4"
CanUserAddRows="False"
CanUserSortColumns="True"
ItemsSource="{Binding Path=PeopleView}" />
</Grid>
</Window>
View models:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using AgentOctal.WpfLib;
namespace FilterWithBindableCount
{
class MainWindowVm : ViewModel
{
public MainWindowVm()
{
People = new ObservableCollection<PersonVm>();
PeopleView = (CollectionView) CollectionViewSource.GetDefaultView(People);
PeopleView.Filter = obj =>
{
var person = (PersonVm)obj;
return person.FirstName.ToUpper().Contains(FilterText.ToUpper() ) || person.LastName.ToUpper().Contains(FilterText.ToUpper());
};
People.Add(new PersonVm() { FirstName = "Bradley", LastName = "Uffner" });
People.Add(new PersonVm() { FirstName = "Fred", LastName = "Flintstone" });
People.Add(new PersonVm() { FirstName = "Arnold", LastName = "Rimmer" });
People.Add(new PersonVm() { FirstName = "Jean-Luc", LastName = "Picard" });
People.Add(new PersonVm() { FirstName = "Poppa", LastName = "Smurf" });
}
public ObservableCollection<PersonVm> People { get; }
public CollectionView PeopleView { get; }
private string _filterText = "";
public string FilterText
{
get => _filterText;
set
{
if (SetValue(ref _filterText, value))
{
PeopleView.Refresh();
}
}
}
}
class PersonVm:ViewModel
{
private string _firstName;
public string FirstName
{
get {return _firstName;}
set {SetValue(ref _firstName, value);}
}
private string _lastName;
public string LastName
{
get {return _lastName;}
set {SetValue(ref _lastName, value);}
}
}
}
This is actually significantly easier when properly following MVVM. The CollectionView is either declared in the XAML, or as a property in the viewmodel. This allows you to bind directly to CollectionView.Count.
Here is an example of how to place the CollectionViewSource in XAML from one of my apps:
<UserControl
x:Class="ChronoPall.App.TimeEntryList.TimeEntryListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:ChronoPall.App"
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ChronoPall.App.TimeEntryList"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance local:TimeEntryListViewVm}"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<UserControl.Resources>
<CollectionViewSource x:Key="TimeEntriesSource" Source="{Binding Path=TimeEntries}">
<CollectionViewSource.SortDescriptions>
<componentModel:SortDescription Direction="Descending" PropertyName="StartTime.Date" />
<componentModel:SortDescription Direction="Ascending" PropertyName="StartTime" />
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="EntryDate" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</UserControl.Resources>
<Grid IsSharedSizeScope="True">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Source={StaticResource TimeEntriesSource}}">
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate DataType="{x:Type CollectionViewGroup}">
<local:TimeEntryListDayGroup />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:TimeEntryListItem />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
It doesn't actually bind to Count, but it could easily do that with:
<TextBlock Text="{Binding Path=Count, Source={StaticResource TimeEntriesSource}}/>
To do it in the viewmodel, you would just create a readonly property of ICollectionView, and set it equal to CollectionViewSource.GetDefaultView(SomeObservableCollection‌​), then bind to that.

ReactiveUI 6 with listview not updating

Been tinkering with reactiveui and trying to get a simple example working in a universal app and having some diffs.
I have viewmodel as:
public class Rooms : ReactiveObject
{
private RoomsService roomService;
private IReactiveCommand<IList<Room>> fetchRooms;
private ObservableAsPropertyHelper<IList<Room>> myRooms;
public IList<Room> MyRooms
{
get { return myRooms.Value; }
}
public Rooms(RoomsService roomsService)
{
this.RoomService = roomsService;
fetchRooms = ReactiveCommand.CreateAsyncTask<IList<Room>>(Observable.Return<bool>(true), async x => await RoomService.Rooms);
fetchRooms.Subscribe(_ => LogMessage("Cool, it was invoked!"));
fetchRooms.ToProperty(this, x => x.MyRooms, out myRooms);
}
public RoomsService RoomService
{
get { return roomService; }
set { roomService = value; }
}
}
and the service that is ticking getting info is:
public class RoomsService
{
private FakeRoomService service = new FakeRoomService();
public IObservable<IList<Room>> Rooms
{
get
{
LogMessage("RoomsService - Rooms property called.");
return Observable
.Interval(TimeSpan.FromSeconds(1))
.Select(_ => service.GetRoomsForUser());
}
}
}
with a basic code behind:
public sealed partial class HubPage : Page
{
private NavigationHelper navigationHelper;
private Rooms rooms = new Rooms(new RoomsService());
/// <summary>
/// Gets the NavigationHelper used to aid in navigation and process lifetime management.
/// </summary>
public NavigationHelper NavigationHelper
{
get { return this.navigationHelper; }
}
/// <summary>
/// Gets the rooms view model.
/// </summary>
public Rooms Rooms
{
get { return this.rooms; }
}
public HubPage()
{
this.InitializeComponent();
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
}
and the final xaml:
<Page
x:Name="pageRoot"
x:Class="HubPage"
DataContext="{Binding Rooms, RelativeSource={RelativeSource Self}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Hub"
xmlns:data="using:Hub.Data"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Hub SectionHeaderClick="Hub_SectionHeaderClick">
<HubSection Width="500" x:Uid="Section1Header" Header="Section 1">
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="300" />
</Grid.RowDefinitions>
<ListView Name="RoomListView" Grid.Row="1" ItemsSource="{Binding MyRooms}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</HubSection>
</Hub>
</Grid>
I'm clearly missing something obvious in the viewmodel, but I'm not sure what it is. The task seems to tick over but the the property never gets updated... or if it does, its not propagating through to the view?
Any advice or is there a really simple working example for v6 anywhere?
If you await an Observable, it's roughly equivalent to in Rx:
someObservable.TakeLast(1).Publish(new AsyncSubject<T>()).Subscribe();
Here's the problem though, check out your Observable:
return Observable
.Interval(TimeSpan.FromSeconds(1))
.Select(_ => service.GetRoomsForUser());
It never terminates, so TakeLast never returns!
Wat Do?
Instead, we should just write:
fetchRooms = ReactiveCommand.CreateAsyncTask<IList<Room>>(_ => Task.Run(service.GetRoomsForUser());
Then in the View code-behind, we can rig up the timer to invoke the command:
Observable.Interval(TimeSpan.FromSeconds(5), RxApp.MainThreadScheduler)
.InvokeCommand(this, x => x.ViewModel.FetchRooms);
Why the View code-behind?
Setting up timers and invoking commands that Do Stuff in the ViewModel constructor makes it hard to test, because you have to deploy 3032x mocks to stop a bunch of stuff from happening.
Instead, the View constructor should reach into the VM to kick off these actions, and the VM tests can invoke them when they want to explicitly test them.

Is it possible to Bind a ComboBox ItemsSource to a read only string[]?

I have a combo box in my WPF project and I would like to have it items defined by a readonly string array in my configuration class. This way I would make it very easy to reconfigure the combo box items.
Is it possible to bind my ItemsSource property to a readonly string[]? How it can be done?
MainWindow :
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ComboBox ItemsSource="{Binding List, Source={x:Static local:Configuration.Instance}}"></ComboBox>
</StackPanel>
Configuration File :
public class Configuration
{
// Singleton
private static Configuration _instance;
public static Configuration Instance
{
get
{
if (_instance == null)
_instance = new Configuration();
return _instance;
}
}
public IEnumerable<string> List
{
get
{
return new List<string>()
{
"toto 1",
"toto 2"
};
}
}
public Configuration()
{
}
}
Yes, copy/paste/compile the following:
<ComboBox ItemsSource="Is it possible to Bind a ComboBox (WPF) ItemsSource to a read only string[]"/>

Binding from MVVM to View not working in Prism

I'm new to Prism, but I have successfully built several WPF/Mvvm-Light applications. I'm using ViewModel-first instaciation for each View/ViewModel pair. The views are all loaded and deactivated when the application opens. Views are activated as a result of catching an aggregate event aimed at them. This is the first view I've tried to bind to data in a ViewModel. The view displays as expected, except that my listbox is never populated. Only the outline of the listbox is visible. If I change the background color of the listbox, the color of the empty listbox is changed. The ViewModel property has eight rows but none of them are visible. I am able to display hardcoded items in the list box. I know that the view model is loading into the view as the data context, since another textblock is able to bind to a ViewModel property It must be something broken in my listbox xaml. Here is some xaml to review:
<UserControl
x:Class="DxStudioSelect.View.DxStudioFindView"
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"
>
<UserControl.Resources>
<DataTemplate x:Key="DxStudioListTemplate">
<TextBlock Text="{Binding Path=FriendlyForkName}"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListBox
Grid.Column="0"
ItemsSource="{Binding DatabaseInstanceList}"
ItemTemplate="{StaticResource DxStudioListTemplate}"
/>
<TextBlock Text="{Binding Path=PageName}" Grid.Column="1" FontSize="32" Foreground="Green" TextAlignment="Right"/>
</Grid>
</UserControl>
Here is the code-behind:
public partial class DxStudioFindView : UserControl, IDxStudioFindView {
public DxStudioFindView() {
InitializeComponent();
}
public IViewModel ViewModel {
get { return (IDxStudioFindViewModel)DataContext; }
set { DataContext = value; }
}
}
Here is the ViewModel:
private readonly IEventAggregator _eventAggregator;
private readonly IUnityContainer _unityContainer;
private readonly IRegionManager _regionManager;
private readonly string _dxStudioDatabaseName;
private readonly HeaderUpdatePayload _headerUpdatePayload = new HeaderUpdatePayload("DxStudio", "Select DxStudio Instance");
public DxStudioFindViewModel(IUnityContainer unityContainer, IRegionManager regionManager, IEventAggregator eventAggregator, IDxStudioFindView view)
: base(view) {
_unityContainer = unityContainer;
_regionManager = regionManager;
_eventAggregator = eventAggregator;
View.ViewModel = this;
if(IsInDesignMode) {
//Design-time, so show fake data
DesignTimeDataLoad();
} else {
//Run-time, so do the real stuff
DesignTimeDataLoad();
_dxStudioDatabaseName = LiteralString.DxStudioDatabaseNameTest;
_eventAggregator.GetEvent<ViewChangeRequestEvent>().Subscribe(DxStudioInstanceChangeRequest, ThreadOption.UIThread, false, target => target.TargetView == LiteralString.DxStudioFind);
}
}
public string PageName { get; set; }
//public string PageName { get { return "Find DxStudio Instance"; } }
private ObservableCollection<IDxStudioInstanceDto> _dxStudioInstanceList = null;
public ObservableCollection<IDxStudioInstanceDto> DxStudioInstanceList {
get { return _dxStudioInstanceList; }
set {
_dxStudioInstanceList = value;
OnPropertyChanged("DxStudioInstanceList");
}
}
private void DxStudioInstanceChangeRequest(ViewChangeRequestPayload payload) {
var region = _regionManager.Regions[RegionNames.Content];
region.Activate(View);
_eventAggregator.GetEvent<ViewChangedHeaderEvent>().Publish(_headerUpdatePayload);
var footerUpdatePayload = new FooterUpdatePayload(FooterDisplayMode.DxStudioSelect, _dxStudioDatabaseName, payload.TargetBackDatabase, payload.TargetBack, string.Empty, LiteralString.ToolboxStart);
_eventAggregator.GetEvent<ViewChangedFooterEvent>().Publish(footerUpdatePayload);
}
private void DesignTimeDataLoad() {
PageName = "Find DxStudio Instance";
DxStudioInstanceList = new ObservableCollection<IDxStudioInstanceDto>() {
new DxStudioInstanceDto("Instance1"),
new DxStudioInstanceDto("Instance2"),
new DxStudioInstanceDto("Instance3"),
new DxStudioInstanceDto("Instance4"),
new DxStudioInstanceDto("Instance5"),
new DxStudioInstanceDto("Instance6"),
new DxStudioInstanceDto("Instance7"),
new DxStudioInstanceDto("Instance8"),
};
}
And here is the data transfer object:
public class DxStudioInstanceDto : IDxStudioInstanceDto {
public string FriendlyForkName { get; private set; }
public DxStudioInstanceDto(string friendlyForkName) { FriendlyForkName = friendlyForkName; }
}
Since I'm completely out of ideas, any suggestion would be helpful.
Thanks
Your list is binding to ItemsSource="{Binding DatabaseInstanceList}" but your view model has the property DxStudioInstanceList.

MVVM Binding a combobox

I have a very ordinary ViewModel and I am tring to bind a collection of values to a combobox. The problem is nothing is binding. I have checked the ViewModel constructor and the data is being loaded so I suspect its in my XAML but I just cant find out where.
public class OwnerOccupierAccountViewModel : ViewModelBase
{
readonly UserAccountContext _userAccountContext;
readonly LoadOperation<Structure> _loadStructures;
#region Properties
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Structures");
}
}
private Structure _selectedStructure;
public Structure SelectedStructure
{
get { return _selectedStructure; }
set
{
_selectedStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
#endregion
public OwnerOccupierAccountViewModel()
{
_userAccountContext = new UserAccountContext();
if (!DesignerProperties.IsInDesignTool)
{
_loadStructures = _userAccountContext.Load(_userAccountContext.GetStructuresQuery());
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
}
}
void _loadStructures_Completed(object sender, EventArgs e)
{
_structures = new ObservableCollection<Structure>();
foreach (var structure in _loadStructures.Entities)
{
Structures.Add(structure);
}
}
}
<UserControl.Resources>
<viewmodel:OwnerOccupierAccountViewModel x:Key='ViewModel'></viewmodel:OwnerOccupierAccountViewModel>
</UserControl.Resources>
<ComboBox x:Name='cboApartments'
ItemsSource='{Binding Structures,Source={StaticResource ViewModel},Mode=TwoWay}'
Width='200' />
Try intializing your ObservableCollection of Structures like that:
void _loadStructures_Completed(object sender, EventArgs e)
{
Structures = new ObservableCollection<Structure>(_loadStructures.Entities);
}
and as it was mentioned earlier i think you should change order here:
if (!DesignerProperties.IsInDesignTool)
{
//other code before
//_loadStructures = ...
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
//and now start loading
}
I did similar, very simple app to check what could gone wrong, but everything works well. I will show you my code, so you can compare and maybe you will find some bugs in your solution.
Structure.cs
public class Structure
{
public Structure(string name)
{
Name = name;
}
public string Name { get; set; }
}
StructureService.cs
public class StructureService
{
public void GetAllStructures(Action<IList<Structure>> CompleteCallback)
{
var temp = new List<Structure>()
{
new Structure("Str1"),
new Structure("Str2"),
new Structure("Str3"),
new Structure("Str4"),
new Structure("Str5"),
new Structure("Str6"),
new Structure("Str7")
};
CompleteCallback(temp);
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
protected void RaisePropertyChanged(string prop)
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
OwnerOccupierAccountViewModel:
public class OwnerOccupierAccountViewModel : ViewModelBase
{
StructureService service;
public OwnerOccupierAccountViewModel()
{
if (!DesignerProperties.IsInDesignTool)
{
service = new StructureService();
service.GetAllStructures((result) =>
{
Structures = new ObservableCollection<Structure>(result);
});
}
}
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Stuctures");
}
}
private Structure _selectedStructure;
public Structure SelectedStructure
{
get { return _selectedStructure; }
set
{
_selectedStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
}
MainPage.xaml:
<UserControl x:Class="SilverlightApplication1.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:vm="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<vm:OwnerOccupierAccountViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures,Source={StaticResource ViewModel},Mode=TwoWay}'
SelectedItem="{Binding SelectedStructure, Source={StaticResource ViewModel},Mode=TwoWay}"
Width="100" Height="30">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
If i were in your shoes i wll change xaml to such view:
SuggestedView:
<UserControl x:Class="SilverlightApplication1.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:vm="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<vm:OwnerOccupierAccountViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures, Mode=TwoWay}'
SelectedItem="{Binding SelectedStructure, Mode=TwoWay}"
Width="100" Height="30">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
but i understand that it is somehow impossible in your scenario?
Try replacing this line:
_structures = new ObservableCollection<Structure>();
with this:
Structures = new ObservableCollection<Structure>();
And set the binding of ComboBox to OneWay.
Edited to update solution:
Set DisplayMemberPath property of ComboBox as well:
DisplayMemberPath="StructureName"
The binding will only fire when the property is changed. The line setting the backing variable won't call the RaisePropertyChanged event. Even if it did it would be empty at this point anyway and you'd end up with an empty list.
_structures = new ObservableCollection<Structure>();
When you then add to the collection you aren't changing the property value, you're calling the getter so again the RaisePropertyChanged won't fire.
Structures.Add(structure);
You need to build a local collection then use that as the value for the Structures property. This should cause the binding to be triggered.
var structures = new ObservableCollection<Structure>();
foreach ...
Structures = structures;
You are binding directly to the ViewModel key as a source, but is it set as a DataContext anywhere?

Resources