I am using a bindable StackLayout to show a series of Entry bound to an ObservableCollection<string> (Addresses in the the viewModel down).
It is not a problem to show on the UI the content of the collection, but if I modify the content of any of the Entry, it does not get reflected back in the original ObservableCollection
Here is the view model:
public class MainViewModel
{
public ObservableCollection<string> Addresses { get; set; }
public ICommand AddCommand { get; private set; }
public MainViewModel()
{
AddCommand = new Command(AddEmail);
Addresses = new ObservableCollection<string>();
Addresses.Add("test1");
Addresses.Add("test2");
}
void Add()
{
AddCommand(string.Empty);
}
}
And here is the view:
<?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="TestList.MainPage"
x:Name="page">
<StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Addresses"
FontSize="Large"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center"/>
<Button Command="{Binding AddCommand}"
Text="+" FontSize="Title"
VerticalOptions="Center"/>
</StackLayout>
<StackLayout BindableLayout.ItemsSource="{Binding Addresses}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<Entry Text="{Binding ., Mode=TwoWay}" HorizontalOptions="FillAndExpand"/>
<Button Text="-" FontSize="Title""/>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</ContentPage>
I suspect that this is due to the fact that I am working on strings, and as such they cannot be modified in place. Do you have a suggestion on how to solve this problem without introducing a wrapper class or similar?
If you want to change the value of source in code behind by editing the text in Entry .You need to implement the interface INotifyPropertyChanged in class of ObservableCollection .
Define a model class
public class MyModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
string content;
public string Content
{
get
{
return content;
}
set
{
if (content != value)
{
content = value;
OnPropertyChanged("Content");
}
}
}
}
in ViewModel
public ObservableCollection<MyModel> Addresses { get; set; }
Addresses = new ObservableCollection<MyModel>();
Addresses.Add(new MyModel() {Content = "test1" });
Addresses.Add(new MyModel() { Content = "test2" });
in xaml
<Entry Text="{Binding Content, Mode=TwoWay}" HorizontalOptions="FillAndExpand"/>
You really need a wrapper class for this to work, besides if the syntax is too lengthy you can install PropertyCHanged.Fody package
Then all you need to do is add this tag:
[AddINotifyPropertyChangedInterface]
public class MainViewModel
{
public List<Address> Addresses { get; set; }
And in the wrapper class:
[AddINotifyPropertyChangedInterface]
public class Address
{
public string Street { get; set; }
Related
I'm using WinUI in combination with the microsoft MVVM toolkit.
However im experiencing some issues with Binding and can't figure out where the problem lies.
The ViewModel and models used within the ViewModel are of type observableObject. The Command is fired, and the data is fetched. However the binding is not showing a result in the UI, unless i change the xaml and hot reload the change.
My page:
<Page
x:Class="ThrustmasterGuide.Pages.WheelBasePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ThrustmasterGuide.Pages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:ThrustmasterGuide.DataAccess.Context.Model"
xmlns:wheelbase="using:ThrustmasterGuide.ViewModel.Wheelbase"
xmlns:xaml="using:ABI.Microsoft.UI.Xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:converters="using:ThrustmasterGuide.Converters"
xmlns:wheelBase="using:ThrustmasterGuide.Model.WheelBase"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance wheelbase:WheelBaseViewModel, IsDesignTimeCreatable=True}">
<Page.Resources>
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:InvertBoolToVisibilityConverter x:Key="InvertBoolToVisibilityConverter" />
</Page.Resources>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Loaded">
<core:EventTriggerBehavior.Actions>
<core:InvokeCommandAction Command="{x:Bind ViewModel.LoadWheelBaseCommand}" />
</core:EventTriggerBehavior.Actions>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<StackPanel Padding="16 16 16 16" Orientation="Vertical">
<StackPanel>
<TextBlock FontSize="18" Text="{x:Bind ViewModel.WheelBase.Name}" />
<TextBlock FontSize="18" Text="Symptomen:" />
<TextBlock Text="Kies hieronder een symptoom uit om te starten." />
<ProgressRing IsActive="true"
Visibility="{x:Bind ViewModel.LoadWheelBaseCommand.IsRunning, Converter={StaticResource BoolToVisibilityConverter}}" />
<TreeView ItemsSource="{x:Bind ViewModel.WheelBase.Symptoms, Mode=OneWay}">
<TreeView.ItemTemplate>
<DataTemplate x:DataType="wheelBase:SymptomModel">
<TreeViewItem ItemsSource="{x:Bind Children}" Content="{x:Bind Description}" />
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
<Button VerticalAlignment="Bottom" Command="{x:Bind ViewModel.LoadWheelBaseCommand}"
Content="Refresh">
</Button>
</StackPanel>
My ViewModel:
public class WheelBaseViewModel : ObservableRecipient
{
public WheelBaseModel WheelBase { get; set; }
public string WheelBaseName { get; set; }
private readonly WheelBaseService _wheelBaseService;
public IAsyncRelayCommand LoadWheelBaseCommand { get; }
public WheelBaseViewModel(WheelBaseService wheelBaseService)
{
_wheelBaseService = wheelBaseService;
LoadWheelBaseCommand = new AsyncRelayCommand(FetchWheelBase);
}
public async Task FetchWheelBase()
{
WheelBase = await _wheelBaseService.GetWheelBase(WheelBaseName);
}
}
My model:
namespace ThrustmasterGuide.Model.WheelBase
{
public class WheelBaseModel : ObservableObject
{
public string Name { get; set; }
public ObservableCollection<SymptomModel> Symptoms { get; set; }
}
}
My Code behind:
public sealed partial class WheelBasePage : Page
{
public WheelBasePage()
{
this.InitializeComponent();
this.DataContext = App.Current.Services.GetService<WheelBaseViewModel>();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
this.ViewModel.WheelBaseName = e.Parameter as string;
}
public WheelBaseViewModel ViewModel => (WheelBaseViewModel)DataContext;
}
What is it that i missed to make the UI bind to the WheelBaseModel values?
Update I added mode=OneWay, but still not updating.
Should it be noted that im showing pages within a content frame after navigation?
{x:Bind} has a default mode of OneTime, unlike {Binding}, which has a default mode of OneWay.
I believe you need to use SetProperty so it's known when to raise such events?
https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/observableobject
namespace ThrustmasterGuide.Model.WheelBase
{
public class WheelBaseModel : ObservableObject
{
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
public ObservableCollection<SymptomModel> Symptoms { get; set; }
}
and also bind mode = OneWay
<TextBlock FontSize="18" Text="{x:Bind ViewModel.WheelBase.Name, Mode=OneWay}" />
No indication that your properties notify of their changes.
https://learn.microsoft.com/dotnet/api/system.componentmodel.inotifypropertychanged
I'm currently trying to bind a few properties when I click a button and it pushes a new page.
Starting from the top, this is how my app is setup
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}
I have a "MainPage" which is essentially the first page that shows when starting the app.
In my MainPage.xaml
I've set the BindingContext to the MainViewModel
<ContentPage.BindingContext>
<viewModels:MainViewModel/>
</ContentPage.BindingContext>
And I also have a button which has it's Command bound to a Command in my MainViewModel
<Button Text="New Goal"
HeightRequest="50" WidthRequest="100"
TextColor="White"
Margin="10"
CornerRadius="4"
Command="{Binding NewGoalCommand}">
<Button.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#8FDF70" Offset="0.1" />
<GradientStop Color="#1DBE95" Offset="1.0" />
</LinearGradientBrush>
</Button.Background>
</Button>
The MainViewModel is pretty straightforward. It has a Command property which I initialize in the constructor
public ObservableCollection<Item> Items { get; set; } = new ObservableCollection<Item>();
public Command NewGoalCommand { get; set; }
public MainViewModel()
{
NewGoalCommand = new Command(() => ShowNewGoalPage());
}
private async void ShowNewGoalPage()
{
await Application.Current.MainPage.Navigation.PushAsync(new NewGoal(SelectedItem));
}
NewGoal.xaml
As you can see in the code it's invoking NewGoal which is my second page which shows up when I click the button, this page does show up when I click the button which to me, indicates that the binding was successful.
The same goes for this page, I'm setting the BindingContext to my other ViewModel which is responsible for it's corresponding view, like so
<ContentPage.BindingContext>
<viewModel:NewGoalViewModel/>
</ContentPage.BindingContext>
And I've also added components which are going to bind to it's corresponding property so that when I click "Save" it adds that item to the collection inside the MainViewModel
<ContentPage.Content>
<StackLayout Spacing="0">
<Image WidthRequest="50" HeightRequest="50"
Margin="10"
Source="{Binding ItemModel.ImageSource}"/>
<StackLayout Margin="20,0,20,0"
Spacing="0">
<Label Text="Title"/>
<Entry />
</StackLayout>
<StackLayout Margin="20,20,20,0"
Spacing="0">
<Label Text="Description"/>
<Entry />
</StackLayout>
<StackLayout Margin="20,20,20,0"
Spacing="0">
<Label Text="Type"/>
<Picker ItemsSource="{Binding ItemModel.Type}" />
</StackLayout>
<StackLayout Margin="20,20,20,0"
Spacing="0">
<Label Text="Price"/>
<Entry Keyboard="Numeric"/>
</StackLayout>
<Button Command="{Binding SaveCommand}"
Text="Save"
VerticalOptions="End">
</Button>
</StackLayout>
</ContentPage.Content>
And here is the NewGoalViewModel
class NewGoalViewModel : MainViewModel
{
public Item ItemModel { get; set; }
public Command SaveCommand { get; set; }
public NewGoalViewModel()
{
ItemModel = new Item();
ItemModel.Title = "Title";
ItemModel.Description = "Description";
ItemModel.Type = SavingsType.Other;
ItemModel.Price = 19.00f;
ItemModel.ImageSource = "cash.jpg";
SaveCommand = new Command(() => AddGoal());
}
private void AddGoal()
{
Items.Add(new Item { Title = "Rainy Day", Type = SavingsType.Other, Price = 100.00, ImageSource = "cash.jpg" });
}
}
The issue
So when I start the app and I click the first button, it takes me to the next page.
when I land on that page, it should show me an Image at the top. It's bound to a property which I've assigned in the constructor ItemModel.ImageSource = "cash.jpg";
The issue is however is that it doesnt actually bind, resulting in the image not showing until I save the NewGoal.xaml page and it reloads. Once it's done reloading it shows the image.
Try modifying
private Item itemModel { get; set; }
public Item ItemModel
{
get { return itemModel ; }
set
{
itemModel = value;
OnPropertyChanged();
}
}
and inherit your ViewModel to : INotifyPropertyChanged
and add this to implement
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Trying to fire a command inside a stacklayout with itemssource. I wonder why the NavigateToProductListViewShopTappedCommand is not getting fired.
Tried multiple command approaches:
1)
Command="{Binding Source={RelativeSource AncestorType={x:Type local:MyShopsListViewModel}}, Path=NavigateToProductListViewShopTappedCommand}"
Command="{Binding BindingContext.NavigateToProductListViewShopTappedCommand, Source={x:Reference Page}}"
Command="{Binding Path=BindingContext.NavigateToProductListViewShopTappedCommand, Source={x:Reference Page}}"
None are not working
Code:
<?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:controls="clr-namespace:BoerPlaza.Controls.Shop"
xmlns:local="clr-namespace:BoerPlaza.ViewModels"
xmlns:behaviors="clr-namespace:BoerPlaza.Behaviors"
x:Class="BoerPlaza.Views.Shop.MyShopsPage"
x:Name="Page"
Title="Mijn winkels">
<ContentPage.Content>
<ScrollView>
<StackLayout Margin="{StaticResource margin-side-std}"
Padding="{StaticResource padding-top-bottom-std}"
BindableLayout.ItemsSource="{Binding Shops}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<controls:ShopCardTemplateView Shop="{Binding .}"
ControlTemplate="{StaticResource ShopCardTemplateView}">
<controls:ShopCardTemplateView.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1"
Command="{Binding Source={RelativeSource AncestorType={x:Type local:MyShopsListViewModel}}, Path=NavigateToProductListViewShopTappedCommand}"
CommandParameter="{Binding .}">
<!--Command="{Binding BindingContext.NavigateToProductListViewShopTappedCommand, Source={x:Reference Page}}"-->
</TapGestureRecognizer>
</controls:ShopCardTemplateView.GestureRecognizers>
</controls:ShopCardTemplateView>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
Code behind
public partial class MyShopsPage : ContentPage
{
private readonly MyShopsListViewModel _viewModel;
public MyShopsPage()
{
InitializeComponent();
BindingContext = _viewModel = new MyShopsListViewModel(App.ShopDataStore, App.DialogService);
_viewModel.LoadShopsOnUserIdCommand.Execute("B22698B8-42A2-4115-9631-1C2D1E2AC5F7");
}
protected override void OnAppearing()
{
base.OnAppearing();
_viewModel.OnAppearing();
}
}
ViewModel:
[QueryProperty(nameof(UserId), nameof(UserId))]
public class MyShopsListViewModel : BaseViewModel
{
private string _userId;
private ObservableCollection<ShopDbViewModel> _shops;
private readonly IShopDataStore _shopDataStore;
private readonly IDialogService _dialogService;
public ObservableCollection<ShopDbViewModel> Shops
{
get
{
return _shops;
}
set
{
_shops = value;
OnPropertyChanged(nameof(Shops));
}
}
public void OnAppearing()
{
IsBusy = true;
}
public ICommand LoadShopsOnUserIdCommand { get; set; }
public ICommand NavigateToProductListViewShopTappedCommand { get; set; }
public MyShopsListViewModel(IShopDataStore shopDataStore, IDialogService dialogService)
{
this._shopDataStore = shopDataStore;
this._dialogService = dialogService;
Shops = new ObservableCollection<ShopDbViewModel>();
LoadShopsOnUserIdCommand = new Command<string>(async (string userId) => await ExecuteLoadShopsOnUserId(userId));
NavigateToProductListViewShopTappedCommand = new Command<ShopDbViewModel>(async (ShopDbViewModel shop) => await ExecuteNavigateToProductListViewShopTappedCommandAsync(shop));
}
private async Task ExecuteNavigateToProductListViewShopTappedCommandAsync(ShopDbViewModel shop)
{
if (shop == null)
return;
await Shell.Current.GoToAsync($"{nameof(ProductsPage)}?{nameof(MyProductsListViewModel.ShopId)}={shop.Id}");
}
public string UserId
{
get
{
return _userId;
}
set
{
_userId = value;
LoadShopsOnUserIdCommand.Execute(value);
}
}
private async Task ExecuteLoadShopsOnUserId(string userId)
{
var current = Connectivity.NetworkAccess;
if (current == NetworkAccess.Internet)
{
try
{
Shops.Clear();
var shops = await _shopDataStore.GetShopOnUserIdAsync(userId);
foreach(var shop in shops)
{
Shops.Add(shop);
}
}
catch (Exception ex)
{
await _dialogService.ShowDialog(ex.Message, "An error has occurred", "OK");
}
finally
{
IsBusy = false;
}
}
else
{
await _dialogService.ShowDialog("No active internet connection", "Connection error", "OK");
IsBusy = false;
}
}
}
If you define the ICommand in the ViewModel directly , you could set the binding path like following
Command="{Binding Path=BindingContext.NavigateToProductListViewShopTappedCommand, Source={x:Reference Page}}"
I've found the problem
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ffimage="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:customcontrols="clr-namespace:BoerPlaza.Controls"
x:Class="BoerPlaza.Controls.Shop.ShopCardTemplateView">
<ContentView.Resources>
<ControlTemplate x:Key="ShopCardTemplateView">
<!-- Card Header -->
<!-- for displaying products and categories on homepage -->
<StackLayout Spacing="1"
HorizontalOptions="FillAndExpand"
Margin="{StaticResource margin-card}">
<!-- On click - shows the product detail view page -->
<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" />
</StackLayout.GestureRecognizers>
<!-- Image frame -->
<Frame BackgroundColor="{StaticResource image-box-color}"
CornerRadius="0"
HasShadow="False"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
HeightRequest="100">
<!-- Product Image -->
....
As you can see this is the control template I'm using for the MyShopsPage. This is a shop card. Inside this shop card I already had an StackLayout.GestureRecognizers. Somehow when I was clicking on the control template, I was actually clicking on this.
I always thought everything flows from top to bottom in events, but this seems different. Something that is on top on something else does not mean anything in xaml.
I am new to Xamarin.Forms development, so I apologize if this is a basic question.
Below is the out-of-the-box code for a Xamarin.Forms Master-Detail page "ItemsPage.xaml"
I would like to add a Picker above the dynamically displayed list of items.
Question 1) What XAML should I insert to add a Picker? I've tried a few variations, but the xaml keeps complaining.
When a user selects an option in the Pickerm, I would like to filter the displayed list in the CollectionView to only show the items that are of the type that the user chose in the Picker.
Question 2) How would I put that onchange behavior to apply a filter to the displayed list?
Thank you advance for any advice or sample code!
<?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="NB_App.Views.ItemsPage"
Title="{Binding Title}"
x:Name="BrowseItemsPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Clicked="AddItem_Clicked" />
</ContentPage.ToolbarItems>
<!-- This is where I want to insert the Picker -->
<RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadItemsCommand}">
<CollectionView x:Name="ItemsCollectionView" ItemsSource="{Binding Items}" >
<CollectionView.ItemTemplate>
You will bind ItemsSource of CollectionView when Picker raises SelectedIndexChanged event
<?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="App9.MainPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Clicked="AddItem_Clicked" />
</ContentPage.ToolbarItems>
<StackLayout>
<Picker Title="My Picker" SelectedIndexChanged="Picker_SelectedIndexChanged">
<Picker.Items>
<x:Array>
<x:String>A</x:String>
<x:String>B</x:String>
<x:String>C</x:String>
<x:String>D</x:String>
</x:Array>
</Picker.Items>
</Picker>
<!-- This is where I want to insert the Picker -->
<RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadItemsCommand}">
<CollectionView x:Name="ItemsCollectionView" />
</RefreshView>
</StackLayout>
</ContentPage>
Code behind
private void Picker_SelectedIndexChanged(object sender, EventArgs e)
{
var picker = sender as Picker;
switch (picker.SelectedIndex)
{
case 0:
ItemsCollectionView.ItemsSource = new ObservableCollection<string>() { "apple", "america"};
break;
case 1:
ItemsCollectionView.ItemsSource = new ObservableCollection<string>() { "ball", "basket" };
break;
case 2:
ItemsCollectionView.ItemsSource = new ObservableCollection<string>() { "cat", "coronavirus" };
break;
case 3:
ItemsCollectionView.ItemsSource = new ObservableCollection<string>() { "dog", "disease" };
break;
default:
break;
}
}
You can do this through MVVM with commands as well. Since you are novice code behind approach should help learn it.
According to your description, you want to add Picker to select item to filter Collectionview, am I right?
If yes, I do one sample that you can take a look:
<ContentPage
x:Class="demo3.collectionviewsample.Page2"
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">
<ContentPage.Content>
<StackLayout>
<Picker
ItemDisplayBinding="{Binding .}"
ItemsSource="{Binding FilterOptions}"
SelectedItem="{Binding SelectedFilter}" />
<CollectionView ItemsSource="{Binding FilterSource}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}" />
<Label Text="{Binding Role}" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage.Content>
public partial class Page2 : ContentPage
{
public Page2()
{
InitializeComponent();
this.BindingContext = new ItemViewModel();
}
}
ViewModel:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Role { get; set; }
}
public class ItemViewModel:ViewModelBase
{
public ObservableCollection<string> FilterOptions { get; set; }
public ObservableCollection<Item> source { get; set; }
public List<Item> _FilterSource;
public List<Item> FilterSource
{
get { return _FilterSource; }
set
{
_FilterSource = value;
RaisePropertyChanged("FilterSource");
}
}
private string _SelectedFilter;
public string SelectedFilter
{
get { return _SelectedFilter; }
set
{
_SelectedFilter = value;
RaisePropertyChanged("SelectedFilter");
filteritems(SelectedFilter);
}
}
public ItemViewModel()
{
FilterOptions = new ObservableCollection<string>()
{
"All",
"Admin",
"Editor",
"Student"
};
source = new ObservableCollection<Item>()
{
new Item() { Id = 1, Name = "cherry", Role = "Admin" },
new Item() { Id = 2, Name = "barry", Role = "Admin" },
new Item() { Id = 3, Name = "json", Role = "Editor" },
new Item() { Id = 3, Name = "json", Role = "Editor" },
new Item() { Id = 5, Name = "Leo", Role = "Student" },
new Item() { Id = 6, Name = "Cole", Role = "Student" }
};
FilterSource = new List<Item>();
}
private void filteritems(string selecteditem)
{
FilterSource.Clear();
if(selecteditem=="All")
{
FilterSource = source.ToList();
}
else
{
FilterSource = source.Where(item => item.Role == selecteditem).ToList();
}
}
}
ViewModelBase is the class that implementing INotifyPropertyChanged interface, to inotify data changed.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The Screenshot:
If I bound my ListBox to ViewModels ObservableCollection or XAML resourced CollectionViewSource, the mock data shows while in design.
Sometimes CollectionViewSource stops showing this data because of some XAML changes, but after rebuilding the code it fills controls back with fake data again.
Grouping, sorting and filtering in my case are controlled in ViewModel (and retried from database) so I decided to move over to ICollectionView property based in ViewModel. Unfortunately Views are no longer getting mock data at all.
Here is simple example of my approaches:
<Window x:Class="Test.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:Test"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:MainWindowViewModel }"
Title="MainWindow" Height="100" Width="525"
>
<Window.Resources>
<CollectionViewSource x:Key="ItemsCollectionViewSource" Source="{Binding ItemsObservableCollection}"/>
</Window.Resources>
<UniformGrid Columns="6">
<ListBox ItemsSource="{Binding ItemsObservableCollection}" Background="WhiteSmoke" />
<ListBox ItemsSource="{Binding Source={StaticResource ItemsCollectionViewSource}}" Background="LightYellow" />
<ListBox ItemsSource="{Binding ItemsICollectionView}" Background="WhiteSmoke" />
<ListBox ItemsSource="{Binding ItemsCollectionView}" Background="LightYellow" />
<ListBox ItemsSource="{Binding ItemsListCollectionView}" Background="WhiteSmoke" />
<ListBox ItemsSource="{Binding ItemsBackCollectionViewSource}" Background="LightYellow" />
</UniformGrid>
</Window>
code behind:
namespace Test
{
public partial class MainWindow
{
public MainWindow()
{
DataContext = new MainWindowViewModel();
InitializeComponent();
}
}
}
and ViewModel:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
namespace Test
{
public class MainWindowViewModel
{
public ICollectionView ItemsICollectionView { get; set; }
public CollectionView ItemsCollectionView { get; set; }
public ListCollectionView ItemsListCollectionView { get; set; }
public ObservableCollection<string> ItemsObservableCollection { get; set; }
public CollectionViewSource ItemsBackCollectionViewSource { get; set; }
public MainWindowViewModel()
{
ItemsObservableCollection = new ObservableCollection<string> {"a", "b", "c"};
ItemsICollectionView = CollectionViewSource.GetDefaultView(ItemsObservableCollection);
ItemsCollectionView = CollectionViewSource.GetDefaultView(ItemsObservableCollection) as CollectionView;
ItemsListCollectionView = CollectionViewSource.GetDefaultView(ItemsObservableCollection) as ListCollectionView;
ItemsBackCollectionViewSource = new CollectionViewSource {Source = ItemsObservableCollection};
}
}
}
None of methods I have tried in order to move CollectionViewSource to ViewModel allows me to see mock data:
I did some debug comparison on those controls, but they are set pretty same in a run time. I'm not aware of ability to debug at design time.
Is there something I'm missing, or it has to be that way?
Thanks