Xamarin UWP seems to bind to the wrong view model - c#

I have a Xamarin project for Android and UWP. This issue seems to only happen on UWP.
In my Xamarin project I have ContentPage with a view model bound as context. In this ViewModel there's an ObservableCollection with another kind of view model. When I create a new instance of this underlying ViewModel and add to my ObservableCollection, sometimes the ContentPage works as expected, showing an item in my ListView. But sometimes there's an empty element added, that I can see when hovering over the list. When this happens I get a bunch of warnings in the Output tab.
My DownloadsPage:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Downloader.Converters"
mc:Ignorable="d"
x:Class="Downloader.Views.DownloadsPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:DownloadStatusToColorConverter x:Key="downloadStatusToColor" />
</ResourceDictionary>
</ContentPage.Resources>
<RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadItemsCommand}">
<ListView x:Name="DownloadsListView" SelectionMode="None" ItemsSource="{Binding Downloads}" RowHeight="70">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="10" BackgroundColor="{Binding DownloadStatus, Converter={StaticResource downloadStatusToColor}}">
<Label Text="{Binding Name}"
d:Text="{Binding .}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16" />
<Grid Grid.Row="0" Grid.Column="0" Padding="10,0,10,0">
<ProgressBar BackgroundColor="Transparent" Progress="{Binding PercentDownloaded}" HorizontalOptions="FillAndExpand" HeightRequest="20">
</ProgressBar>
<Label Text="{Binding PercentString}" HorizontalTextAlignment="Center"></Label>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</RefreshView>
</ContentPage>
DownloadsViewModel is set as context in the code-behind like this:
public partial class DownloadsPage : ContentPage
{
private readonly DownloadsViewModel _viewModel;
public DownloadsPage()
{
InitializeComponent();
BindingContext = _viewModel = new DownloadsViewModel();
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
Device.BeginInvokeOnMainThread(() => _viewModel.RefreshDownloads());
return true;
});
}
}
The bound DownloadsViewModel:
public class DownloadsViewModel : BaseViewModel
{
public ObservableCollection<DownloadViewModel> Downloads { get; set; } = new ObservableCollection<DownloadViewModel>();
public Command LoadItemsCommand { get; set; }
public DownloadsViewModel()
{
Title = "Downloads";
LoadItemsCommand = new Command(() => {
IsBusy = true;
Downloads.Clear();
RefreshDownloads();
IsBusy = false;
});
}
public void RefreshDownloads()
{
foreach (var download in DownloadManager.GetDownloads())
{
var existingDownload = Downloads.FirstOrDefault(d => d.Id == download.Id);
if (existingDownload != null)
{
existingDownload.UpdateValues(download);
}
else
{
Downloads.Add(new DownloadViewModel(download));
}
}
}
}
And the ObservableCollection contains DownloadViewModel that looks like this:
public class DownloadViewModel : BaseViewModel
{
private IDownload _download;
public DownloadViewModel(IDownload download)
{
UpdateValues(download);
}
private string _id;
public string Id
{
get { return _id; }
set { SetProperty(ref _id, value); }
}
private string _name;
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
private DownloadStatus _status;
public DownloadStatus DownloadStatus
{
get { return _status; }
set { SetProperty(ref _status, value); }
}
public double PercentDownloaded
{
get
{
return _download.DownloadedBytes == -1
? 0f
: (double)_download.DownloadedBytes / _download.TotalBytes;
}
}
public string PercentString { get => $"{(int)(PercentDownloaded * 100)} %"; }
public void UpdateValues(IDownload download)
{
_download = download;
Id = _download.Id;
Name = _download.Name;
DownloadStatus = _download.Status;
}
}
The error I sometimes get which causes items in my ListView to be empty:
Binding: 'DownloadStatus' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.StackLayout.BackgroundColor'
Binding: 'Name' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.Label.Text'
Binding: 'PercentDownloaded' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.ProgressBar.Progress'
Binding: 'PercentString' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.Label.Text'
When debugging I've confirmed that the item is added to my ObservableCollection as expcted.
How come sometimes it's looking for DownloadStatus, Name, PercentDownloaded and PercentString on DownloadsViewModel instead of DownloadViewModel?

Xamarin UWP seems to bind to the wrong view model
I checked your code sample and it works as expect. But I found the progress value does not update automatically that cause the listview item can't display, I have update the IDownload interface add PercentDownloaded property. For the testing it could works in uwp platform.
The problem was that the ViewModels did not have setters with INotifyPropertyChanged implemented for all properties. The source code is available on Github, and the commit that fixes the issue is this one.

Related

Xamarin Forms Picker Binding breaks when Itemssource changes

I have a problem with Binding Data to a Picker in Xamarin Forms and hpe somebody can help me. I have a ContentPage which holds a picker. The Itemssource for that Picker is queried async from a web service. Also, the view (or rather, the ViewModel) is passed a seleted item. For whatever reason, setting the itemssource breaks the binding of the SelectedItem property.
Here is my ViewModel -
public class ExerciseViewModel:BaseViewModel
{
private ApiServices apiService = new ApiServices();
private Exercise exercise;
public Exercise Exercise
{
get => exercise;
set
{
exercise = value;
OnPropertyChanged();
}
}
private List<ExerciseCategory> exerciseCategories = new List<ExerciseCategory>();
public List<ExerciseCategory> ExerciseCategories
{
get => exerciseCategories;
set
{
exerciseCategories = value;
OnPropertyChanged();
}
}
public ExerciseViewModel()
{
GetCategoriesCommand.Execute(null);
Exercise = new Exercise() { Name = "Neue Übung", Category = ExerciseCategories.FirstOrDefault() };
}
public ExerciseViewModel(Exercise ex)
{
Exercise = ex;
GetCategoriesCommand.Execute(null);
}
public ICommand GetCategoriesCommand
{
get
{
return new Command(async () =>
{
ExerciseCategories = await apiService.GetExerciseCategories();
});
}
}
public ICommand AddExerciseCommand
{
get
{
return new Command(async () =>
{
Exercise.Id = await apiService.AddExercise(Exercise);
});
}
}
}
This is the Entity in question - the necessary operators are overloaded, INotifyPropertyChanged is implemented in the BaseClass -
public class ExerciseCategory:BaseClass
{
private string name;
private int id;
[Key]
public int Id
{
get => id;
set
{
id = value;
OnPropertyChanged();
}
}
public string Name
{
get => name;
set
{
name = value;
OnPropertyChanged();
}
}
public override bool Equals(object obj)
{
var other = (obj as ExerciseCategory);
if (other is null)
return false;
return this == other;
}
public static bool operator !=(ExerciseCategory e1, ExerciseCategory e2)
{
return !(e1 == e2);
}
public static bool operator ==(ExerciseCategory e1, ExerciseCategory e2)
{
if (e1.Id == e2.Id)
if (e1.Name == e2.Name)
return true;
return false;
}
}
This is the page's CodeBehind:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class NewExercisePage : ContentPage
{
public NewExercisePage(ExerciseViewModel viewModel, bool controlsLocked = false)
{
try
{
this.BindingContext = viewModel;
InitializeComponent();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private void UpdateWebView(object sender, TextChangedEventArgs e)
{
Uri uriResult;
bool result = Uri.TryCreate(e.NewTextValue, UriKind.Absolute, out uriResult)
&& (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
if (result)
exerciseVideoViewer.Source = e.NewTextValue;
}
}
Finally, here is the XAML -
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="PerformanceTM.Views.NewExercisePage">
<ContentPage.Content>
<StackLayout>
<WebView x:Name="exerciseVideoViewer" HeightRequest="200" WidthRequest="200"></WebView>
<Grid x:Name="LayoutGrid">
<Label Text="Kategorie" Grid.Column="0" Grid.Row="0"/>
<Picker x:Name="CategoryPicker" ItemsSource="{Binding ExerciseCategories}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding Exercise.Category}" Grid.Column="1" Grid.Row="0"/>
<Label Text="Name" Grid.Column="0" Grid.Row="1"/>
<Entry Text="{Binding Exercise.Name}" Grid.Column="1" Grid.Row="1"/>
<Label Text="Video URL" Grid.Column="0" Grid.Row="2"/>
<Entry Text="{Binding Exercise.VideoUrl}" Grid.Column="1" Grid.Row="2" TextChanged="UpdateWebView"/>
<Label Text="Beschreibung" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="3"/>
<Editor Text="{Binding Exercise.Description}" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="4"/>
</Grid>
<Button Text="Speichern" Command="{Binding AddExerciseCommand}"/>
<Label Text="{Binding Exercise.Category.Name}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
When I select an ExerciseCategory from the CategoryPicker, Binding works. However, the ExerciseCategory that is prvided to the ViewModel via the "ex" parameter does not result in the proper Category being selected in the Picker.
Since the Categories are not (necessarily) present by the time I call InitializeComponent, I suspect this disconnection between the Exercise.Category and the Picker.SelectedItem comes from that late binding. Still, I cannot really figure out how to fix that. Any help is appreciated.

Listview doesn't refresh the item view correctly

I'm developing an app with xamarin forms and the MVVM pattern. I have a page with a listview that has three buttons but all the time with only 2 visibles and change the visibility of two of them when I press a button. The problem is that for the first ten items it works like supposed to be, press the button and dissapear and appear the other, but after the 10th item when I press the button it dissapear but the other doesn't appear until I scrool the list view to a position where the item is out of the screen. When the item is out of the screen and come back to be on the screen, the button appear. The visibility of the buttons is controlled changing a boolean property that is binded to the IsVisible property of the button and one of them with a converter to negate the value of the property. This is a repository that you can clone and see the code and test, maybe is something with my Visual Studio.
Initially, I thought it could be for a race condition and made the method that change the variable synchronous but it doesn't work.
This is my list view
<ListView ItemsSource="{Binding Items}"
HasUnevenRows="True"
SeparatorVisibility="None"
IsRefreshing="False">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}"/>
<StackLayout Orientation="Horizontal">
<Button Text="One"
HorizontalOptions="CenterAndExpand"
TextColor="Green"
BackgroundColor="White"
BorderColor="Green"
BorderWidth="1"
WidthRequest="150" />
<Button Text="Two"
HorizontalOptions="CenterAndExpand"
BackgroundColor="Green"
TextColor="White"
Command="{Binding TestCommand}"
WidthRequest="150"
IsVisible="{Binding TestVariable, Converter={StaticResource negate}}" />
<Button Text="Three"
HorizontalOptions="CenterAndExpand"
BackgroundColor="Red"
Command="{Binding TestCommand}"
TextColor="White"
WidthRequest="150"
IsVisible="{Binding TestVariable}" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The viewmodel
public class ListViewTestModel : BaseViewModel
{
private List<ListItemTestModel> items;
public List<ListItemTestModel> Items
{
get => items;
set
{
SetValue(ref items, value);
}
}
public ListViewTestModel()
{
List<ListItemTestModel> itemList = new List<ListItemTestModel>();
for (int i = 0; i < 40; i++)
{
itemList.Add(new ListItemTestModel { Name = "Test" });
}
Items = itemList;
}
}
And another view model that is binded to each item in the listView
public class ListItemTestModel : BaseViewModel
{
private bool testVariable;
public string Name { get; set; }
public bool TestVariable
{
get
{
return testVariable;
}
set
{
SetValue(ref testVariable, value);
}
}
public Command TestCommand { get; set; }
public ListItemTestModel()
{
TestCommand = new Command(() =>
{
TestMethod();
});
}
public void TestMethod()
{
TestVariable = !TestVariable;
}
}
the BaseViewModel
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, value))
{
return;
}
backingField = value;
OnPropertyChanged(propertyName);
}
}
And the codebehind of the page
public partial class MainPage : ContentPage
{
public ListViewTestModel ViewModel { get; }
public MainPage()
{
ViewModel = new ListViewTestModel();
BindingContext = ViewModel;
InitializeComponent();
}
}
I suggest listview Caching Strategy may case this issue, the default value is RetainElement for ListView, so using CachingStrategy="RecycleElement" in ListView.
About listview Caching Strategy, you can take a look:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/performance#caching-strategy
You should definitely go to ObservableCollection type for your items thus you'll be able to observe and display any changes
private ObservableCollection<ListItemTestModel> items;
public ObservableCollection<ListItemTestModel> Items
{
get => items;
set => SetValue(ref items, value);
}
And you should set your BindingContext AFTER the InitializeComponent() method or property changed will be propagate before your view is initialized.
public MainPage()
{
InitializeComponent();
BindingContext = new ListViewTestModel();;
}
public ListViewTestModel()
{
List<ListItemTestModel> itemList = new List<ListItemTestModel>();
for (int i = 0; i < 40; i++)
{
itemList.Add(new ListItemTestModel { Name = "Test" });
}
Items = new ObservableCollection<ListItemTestModel>(itemList);
}

Can't change Databinding in Textblock when selectedItem in listview MVVM

I have a problem . I can't change databind when I selecte Item in list view
This My my code xaml ( View ):
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled" ItemsSource="{Binding FCsource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Border Margin="10" Width="440" Height="220" >
<Grid>
<TextBlock Text="{Binding Words, Mode = TwoWay}"></TextBlock>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
This My my code in ViewModel:
public ObservableCollection _FCsource;
public ObservableCollection FCsource { get { return AddFlashCard(); } set { FCsource = value; OnPropertyChanged(); } }
private Item _SelectedItem;
public Item SelectedItem { get=>_SelectedItem; set
{
_SelectedItem = value;
OnPropertyChanged();
if(_SelectedItem!=null)
{
SelectedItem.Words="hello"
}
}
}
public WordsViewModel()
{
}
private ObservableCollection<Item> AddFlashCard()
{
ObservableCollection<Item> listmn = new ObservableCollection<Item>();
listmn.Add(new Item("qwda");
listmn.Add(new Item("qwda");
listmn.Add(new Item("qwda");
return listmn;
}
With some changes to your code (you have compile errors in there) it works as expected. Predictably, it stops working if there is no INotifyPropertyChanged interface properly implemented on the Item class (specifically signaling changes of the Words property). That is probably what is causing your issue.
Below is the working code (the INotifyPropertyChanged is implemented here using PropertyChanged.Fody version 2.6.0 nuget package):
[AddINotifyPropertyChangedInterface]
public class WordsViewModel
{
public ObservableCollection<WordItem> _FCsource;
public ObservableCollection<WordItem> FCsource { get { return AddFlashCard(); } set { FCsource = value; } }
private WordItem _SelectedItem;
public WordItem SelectedItem
{
get => _SelectedItem; set
{
_SelectedItem = value;
if (_SelectedItem != null)
{
SelectedItem.Words = "hello";
}
}
}
public WordsViewModel()
{
}
private ObservableCollection<WordItem> AddFlashCard()
{
ObservableCollection<WordItem> listmn = new ObservableCollection<WordItem>();
listmn.Add(new WordItem("qwda"));
listmn.Add(new WordItem("qwda"));
listmn.Add(new WordItem("qwda"));
return listmn;
}
}
[AddINotifyPropertyChangedInterface]
public class WordItem
{
public string Words { get; set; }
public WordItem(string words)
{
Words = words;
}
}
If you implemented INotifyPropertyChanged properly and it's still not working, then are you sure that you assigned your View Model to your View's DataContext?

Xamarin.Forms How to two-way bind to a control in Listview

Im having a hard time trying to figure out how to set up a two-way binding for a control inside a listview.
Im using ReactiveUI and Xamarin.Forms.
In this case i would like to load a list of objects that have a quantity. This is set initially when the page loads. However i would like to be able to change these quantity values in the view when the program is run. I used an Entry for that.
Setting up a two-way Binding for the List itself (done in code behind, the reactive way) is not possible. It will error.
Is there another way to observe changes done to the Text property in the Entry control and reflect them to the according item from the list in my viewmodel?
I've been having trouble finding a solution for this and don't really know how to go about this.
Here is my XAML code:
<CustomControls:AutoLoadListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="20,0,0,0" Orientation="Vertical" HorizontalOptions="StartAndExpand">
<Label Margin="0,5,0,-5" Style="{StaticResource ViewCellPrimaryLabelStyle}" x:Name="txt" Text="{Binding itemname}" />
<Label Margin="0,-5,0,5" Style="{StaticResource ViewCellSecondaryLabelStyle}" x:Name="barcode" Text="{Binding productcode}" />
</StackLayout>
<Entry Margin="5,0,5,0" x:Name="quantity" Text="{Binding quantity}">
<Entry.BindingContext>
<ViewModel:AankoopEditViewModel />
</Entry.BindingContext>
</Entry>
<Image Margin="5,5,5,5" x:Name="delete" Source="{Mobile:ImageResource tbin_pos.png}">
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Path=BindingContext.DeleteCommand,Source={x:Reference Name=AankoopEditPage}}"
CommandParameter="{Binding}" />
</Image.GestureRecognizers>
</Image>
</ViewCell>
</DataTemplate>
</CustomControls:AutoLoadListView.ItemTemplate>
My Viewmodel:
public class AankoopEditViewModel : BaseViewModel
{
private VmPurchase Purchase;
public AankoopEditViewModel()
{
PurchaseList = new ReactiveObservableCollection<AankoopEditListItem>()
{
ChangeTrackingEnabled = true
};
this.WhenAnyValue(x => x.PurchaseID).SubscribeOn(RxApp.MainThreadScheduler).Subscribe((x) =>
{
this.Purchase = DatabaseHelper.Purchase.LoadSingleById<VmPurchase>(PurchaseID);
if (Purchase != null)
{
this.Title = Purchase.supplier.name;
using (PurchaseList.SuppressChangeNotifications())
{
foreach (var detail in Purchase.purchasedetails)
{
PurchaseList.Add(new AankoopEditListItem { productcode = detail.item.code, itemname = detail.item.namenl, identifier = detail.key, quantity = detail.quantity.ToString() });
}
}
}
});
try
{
this.WhenAnyValue(x => x.PurchaseList).SubscribeOn(RxApp.MainThreadScheduler).Subscribe((x) =>
{
Console.WriteLine("The List has changed");
});
}
catch (Exception e)
{
return;
}
}
private string _purchaseID;
public string PurchaseID
{
get { return _purchaseID; }
set { this.RaiseAndSetIfChanged(ref _purchaseID, value); }
}
private ReactiveObservableCollection<AankoopEditListItem> _purchases;
public ReactiveObservableCollection<AankoopEditListItem> PurchaseList
{
get
{
return this._purchases;
}
set
{
this.RaiseAndSetIfChanged(ref _purchases, value);
}
}
My Model :
public class AankoopEditListItem : ReactiveObject
{
public string identifier { get; set; }
public string itemname { get; set; }
public string productcode { get; set; }
public string quantity { get; set; }
}
Be careful, when you do this
<Entry.BindingContext>
<ViewModel:AankoopEditViewModel />
</Entry.BindingContext>
you create a new instance of your view model for each item and you bind your Entry to it. Just remove it and keep the binding as it is (Text="{Binding quantity}") if you want to bind your entry to the row view model

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?

Categories