Programmatically populate a stackpanel with an observablecollection of user controls using MVVM - mvvm

I have an observablecollection of type frameworkelement that I would like to display in a stackpanel or something similar. Every item in the observablecollection is a usercontrol that I have created. I'm pretty new to WPF and I don't have any idea how to do this. An example would be much appreciated

I'm borrowing rhe1980's answer a bit here, but the point is that the code in the codebehind will actually be in a viewmodel.
View:
<Window x:Class="Sandbox.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"
Name="mainWindow">
<Grid>
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=MyCollection}"/>
</StackPanel>
</Grid>
CodeBehind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
}
ViewModel:
public class MyViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (!string.IsNullOrEmpty(propertyName))
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
this.OnObjectChanged();
}
private ObservableCollection<FrameworkElement> _myCollection;
public ObservableCollection<FrameworkElement> MyCollection
{
get
{
return _myCollection;
}
set
{
_myCollection = value;
OnPropertyChanged("MyCollection");
}
}
}

Use a ItemsControl for bind the ObservableCollection in the StackPanel:
View(xaml):
<Window x:Class="Sandbox.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"
Name="mainWindow">
<Grid>
<StackPanel>
<ItemsControl ItemsSource="{Binding ElementName=mainWindow,Path=ObservableCollection}"/>
</StackPanel>
</Grid>
Codebehind(xaml.cs):
public partial class MainWindow : Window
{
public ObservableCollection<FrameworkElement> ObservableCollection { get; set; }
public MainWindow()
{
InitializeObservableCollection();
InitializeComponent();
}
private void InitializeObservableCollection()
{
ObservableCollection = new ObservableCollection<FrameworkElement>();
for (var ii = 0; ii < 10; ii++)
{
ObservableCollection.Add(new Button {Content = ii.ToString()});
}
}
}

Related

data is not getting in viewmodel on Icommand button click

I have a form for capturing data from user.
<StackLayout Spacing="10" Margin="20,10,20,0">
<Label Text="Job Name"></Label>
<Entry BackgroundColor="White" x:Name="JobName" Text="{Binding SelectedJob.Name,Mode=TwoWay}"/>
<Label Text="Goal"></Label>
<Entry BackgroundColor="White" x:Name="Goal" Text="{Binding SelectedJob.Goal,Mode=TwoWay}"/>
<Grid ColumnSpacing="8" HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Save" Command="{Binding SubmitCommand}" BackgroundColor="#078fc1" Grid.Column="0" HorizontalOptions="FillAndExpand"/>
<Button Text="Reset" BackgroundColor="#078fc1" Grid.Column="1" HorizontalOptions="FillAndExpand"/>
</Grid>
</StackLayout>
on button click i want to bind data to SelectedJob object.
this is my ViewModel
public class JobViewModel: INotifyPropertyChanged
{
public JobViewModel()
{
SubmitCommand = new Command(OnSubmitAsync);
}
private JobDTO selectedob { get; set; }
public JobDTO SelectedJob
{
get { return selectedob; }
set
{
selectedob = value;
OnPropertyChanged(nameof(SelectedJob));
}
}
public ICommand SubmitCommand { protected set; get; }
public async void OnSubmitAsync()
{
await.jobservice.postjob()
}
}
and this is my model
public class JobDTO:INotifyPropertyChanged
{
private string name { get; set; }
public string Name
{ get { return name;}
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
private string goal;
public string Goal
{
get { return goal; }
set
{
goal = value;
OnPropertyChanged(nameof(Goal));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
on button click i am getting SelectedJob as null,eventhough i made my class as JobDTO:INotifyPropertyChanged.can any one help me what i am doing here wrong.
I changed the code of ViewModel and model.
There is GIF of demo based on your code.
ViewModel of JobViewModel
If you want to used get/set method, one attribute corresponds to one get/set method, I also add OnPropertyChangedmethod.
public class JobViewModel : INotifyPropertyChanged
{
public JobViewModel()
{
SubmitCommand = new Command(OnSubmitAsync);
}
private JobDTO selectedob=new JobDTO();
public JobDTO SelectedJob
{
get { return selectedob; }
set
{
selectedob = value;
OnPropertyChanged("SelectedJob");
}
}
public ICommand SubmitCommand { protected set; get; }
public event PropertyChangedEventHandler PropertyChanged;
public async void OnSubmitAsync()
{
// i donnot know what is your aim of this part, i just pop up a alert that contains Name and Goal from Entry.
await Application.Current.MainPage.DisplayAlert("Alert", "Name: "+SelectedJob.Name+"\n"+ "Goal: "+ SelectedJob.Goal , "Ok");
}
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Model of JobDTO, The changed place is same with ViewModel
public class JobDTO: INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
private string goal;
public string Goal
{
get { return goal; }
set
{
goal = value;
OnPropertyChanged("Goal");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MainPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DateBoundingDemo"
x:Class="DateBoundingDemo.MainPage">
<StackLayout Spacing="10" Margin="20,10,20,0">
<Label Text="Job Name"></Label>
<Entry BackgroundColor="White" x:Name="JobName" Text="{Binding SelectedJob.Name,Mode=TwoWay}"/>
<Label Text="Goal"></Label>
<Entry BackgroundColor="White" x:Name="Goal" Text="{Binding SelectedJob.Goal,Mode=TwoWay}"/>
<Grid ColumnSpacing="8" HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Save" Command="{Binding SubmitCommand}" BackgroundColor="#078fc1" Grid.Column="0" HorizontalOptions="FillAndExpand"/>
<Button Text="Reset" BackgroundColor="#078fc1" Grid.Column="1" HorizontalOptions="FillAndExpand"/>
</Grid>
</StackLayout>
</ContentPage>
MainPage.xaml.cs
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext =new JobViewModel();
}
}

Unable to show Xamarin Forms MVVM binding result in listview

I am trying to implement MVVM approach in my xamarin forms application. During the implementations, I have hit a road block. I am unable to populate the list view with the data that i recieve from the server. I am unable to identify the binding issue.
Please let me know where is my mistake? What am I missing?
View 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"
x:Class="Test.Views.SubtaskPage"
Title="Select Subtask"
xmlns:viewModels="clr-namespace:Test.ViewModels; assembly=Test">
<ContentPage.BindingContext>
<viewModels:SubtaskPageViewModel/>
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem x:Name="tbiAddSubtask" Text="Add Subtask" Clicked="tbiAddSubtask_Clicked"/>
</ContentPage.ToolbarItems>
<StackLayout Orientation="Vertical" Padding="10">
<ListView x:Name="lstSubtasks" ItemSelected="lstSubtasks_ItemSelected" IsPullToRefreshEnabled="True" RefreshCommand="{Binding RefreshCommand}" IsRefreshing="{Binding IsBusy}" ItemsSource="{Binding SubtaskList}}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem x:Name="menuAddTimeSpent" Clicked="menuItem_Clicked" CommandParameter="{Binding Ticket}" Text="Menu" />
</ViewCell.ContextActions>
<StackLayout Padding="20,0,0,0" HorizontalOptions="StartAndExpand" Orientation="Horizontal">
<Label Text="{Binding Subject}" VerticalTextAlignment="Center" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
Response Class Code
public class SubtasksResponse
{
public int Status { get; set; }
public string Message { get; set; }
public List<Ticket> Subtasks { get; set; }
}
View Model Code
public class SubtaskPageViewModel : INotifyPropertyChanged
{
private SubtasksResponse _subtaskList;
public SubtasksResponse SubtaskList
{
get { return _subtaskList; }
set
{
_subtaskList = value;
OnPropertyChanged(nameof(SubtaskList));
}
}
private Command _refreshCommand;
public Command RefreshCommand
{
get
{
return _refreshCommand;
}
}
bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
OnPropertyChanged(nameof(IsBusy));
}
}
public SubtaskPageViewModel()
{
_refreshCommand = new Command(RefreshList);
}
async void RefreshList()
{
SubtaskList = await PopulateSubtaskList();
}
async Task<SubtasksResponse> PopulateSubtaskList()
{
RestService rs = new RestService();
IsBusy = true;
IsBusy = false;
var subtaskList = new SubtasksResponse();
subtaskList = await rs.GetSubtasksAsync(Convert.ToInt32(Application.Current.Properties["UserId"]));
return subtaskList;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
For starters we see you are binding the ListView to ItemsSource="{Binding SubtaskList} - when we then look at the ViewModel it seems that SubtaskList is of type SubtasksResponse, that type only has 3 properties.
But the item template inside your ListView is not using any of those 3 properties... it's using Ticket and Subject.
Are this properties of the class Subtasks? If so you need to bind the ListView directly to the List property for it to pick up the items in that collection.

ViewModelLocator in prism mvvm

Here is my Scenario,I need to create a simple uwp app and I have got a single viewmodel and multiple views..I am using prism mvvm/unity .
MainPage.xaml
<prism:SessionStateAwarePage
x:Class="MvvmSample.Views.MainPage"
xmlns:prism="using:Prism.Windows.Mvvm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MvvmSample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="{Binding Title}" FontSize="29.333" />
<Button Content="Navigate" Command="{Binding del}"/>
</Grid>
Viewmodels.MainpageViewModel
public class MainPageViewModel : ViewModelBase
{
public string Title { get; set; }
public INavigationService NavigateToPage;
public static List<string> names = new List<string>() { "Anzal", "Rashid", "Kamil", "Fahad" };
public ObservableCollection<string> Mynames { get; set; }
public MainPageViewModel(INavigationService navigationservice)
{
this.Title = "Run Time";
NavigateToPage = navigationservice;
for (int i = 0; i < names.Count; i++)
{
Mynames.Add(names[i]);
}
del = new DelegateCommand(
() =>
NavigateToPage.Navigate(App.Expeirences.Second.ToString(),null);
);
}
}
SecondPage.xaml
<prism:SessionStateAwarePage
x:Class="Design3.Views.SecondPage"
xmlns:prism="using:Prism.Windows.Mvvm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Design3"
prism:ViewModelLocator.AutoWireViewModel="True"
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}">
<ListBox ItemsSource="{Binding names}"/>
</Grid>
App.xaml.cs
sealed partial class App : PrismUnityApplication
{
public App()
{
this.InitializeComponent();
}
protected override Task OnInitializeAsync(IActivatedEventArgs args)
{
Container.RegisterInstance<INavigationService>(this.NavigationService);
return base.OnInitializeAsync(args);
}
protected override Task OnLaunchApplicationAsync(LaunchActivatedEventArgs args)
{
this.NavigationService.Navigate(Expeirences.Main.ToString(), null);
Window.Current.Activate();
return Task.FromResult(true);
}
public enum Expeirences
{
Main,
Second
}
}
Now the problem occurs..How can I bind my secondpage to mainpageviewmodel???How to use my ViewModelLocator??
You want to register MainpageViewModel for SecondPage with the ViewModelLocationProvider, thus overriding the convention:
ViewModelLocationProvider.Register<SecondPage, MainpageViewModel>();
...preferably before navigating to the second page :-)

Virtualizing stackpanel - virtualized item visibility

I have a scenario where I am using a list box to display a large list of ViewModels, each with a visibility property that changes depending on application logic.
The problem I am experiencing is that when the visibility of a 'virtualized' item changes, the scrollbar isn't updated to reflect the scrollable range until the items are brought into view by manually scrolling.
This is clearly caused by the fact that the virtualized items are not having the visibility binding evaluated and so do not add to the the scrollable range, but how can I get around the issue without disabling visualization?
Note : I'm aware that I could use a filtering CollectionView, but having the Visibility property works better with my application logic.
Below is some code that demonstrates the problem.
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name { get; private set; }
public Visibility Visibility
{
get { return m_visibility; }
set
{
m_visibility = value;
RaisePropertyChanged("Visibility");
}
}
public ViewModel(string name)
{
Name = name;
}
protected void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
Visibility m_visibility = Visibility.Visible;
}
public partial class MainWindow : Window
{
public List<ViewModel> ViewModels { get; private set; }
public MainWindow()
{
ViewModels = new List<ViewModel>();
for(int i = 0; i < 100; ++i)
{
ViewModels.Add(new ViewModel("item_" + i));
}
DataContext = this;
InitializeComponent();
}
void OnHideItemsClick(object sender, EventArgs e)
{
for (int i = 30; i < ViewModels.Count; ++i)
{
ViewModels[i].Visibility = Visibility.Collapsed;
}
}
void OnShowItemsClick(object sender, EventArgs e)
{
for (int i = 30; i < ViewModels.Count; ++i)
{
ViewModels[i].Visibility = Visibility.Visible;
}
}
}
<DockPanel>
<UniformGrid Columns="2" DockPanel.Dock="Top">
<Button Content="Hide offscreen items" Click="OnHideItemsClick" />
<Button Content="Show offscreen items" Click="OnShowItemsClick" />
</UniformGrid>
<ListBox ItemsSource="{Binding ViewModels}" HorizontalContentAlignment="Stretch">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Visibility" Value="{Binding Visibility}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="1" BorderThickness="1" BorderBrush="Green">
<TextBlock Text="{Binding Name}" Margin="5" />
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>

How to wire up data context in modular application?

I am writing an application, WPF, using PRISM. I'm new so apologies if this question is poor form:
I have a module that up to now has a user control for displaying a list of inspections. My module has entities written and a DbContext class to access DB. My question is where should this get initialsed and passed into my ViewModel???????
Shell XAML
<Window x:Class="ChargeMgm.Desktop.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://www.codeplex.com/prism"
Title="EMS" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition MinHeight="100"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="Calibri"
FontSize="16"
Foreground="SteelBlue"
Margin="5">Street Works Modules</TextBlock>
<Border BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="3" Grid.Row="1" Margin="5">
<ItemsControl prism:RegionManager.RegionName="MainRegion"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"/>
</Border>
</Grid>
</Window>
Bootstrapper class
class Bootstrapper : UnityBootstrapper
{
protected override System.Windows.DependencyObject CreateShell()
{
return new Shell();
}
protected override void InitializeShell()
{
base.InitializeShell();
App.Current.MainWindow = (Window)this.Shell;
App.Current.MainWindow.Show();
}
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
ModuleCatalog moduleCatalog = (ModuleCatalog)this.ModuleCatalog;
moduleCatalog.AddModule(typeof(DefectModule.DefectModule));
}
}
Module
public class DefectModule : IModule
{
private readonly IRegionManager regionManager;
private IUnityContainer container;
public DefectModule(IUnityContainer container, IRegionManager regionManager)
{
this.regionManager = regionManager;
this.container = container;
}
public void Initialize()
{
container.RegisterType<IDefectsView, DefectsView>();
container.RegisterType<IDefectsViewModel, DefectsViewModel>();
container.RegisterType<IDefectContext, DefectContext>();
var view = container.Resolve<IDefectsView>();
if(regionManager.Regions.ContainsRegionWithName("MainRegion"))
{
regionManager.Regions["MainRegion"].Add(view);
//regionManager.RegisterViewWithRegion("MainRegion", typeof(IDefectsView));
}
}
}
If you're using Unity then you're in luck. If you need it initialise you DB context then you can do something like this:
IModule implementation code (for your module)
// Create Module http://msdn.microsoft.com/en-us/library/ff648781.aspx
public class Module:IModule
{
private IUnityContainer _container;
public Module(IUnityContainer container,IRegionManager regionManager)
{
_regionManager=regionManager;
_container=container;
}
public Initialize()
{
_container.RegisterType<IView,View>();
_container.RegisterType<IViewModel,ViewModel>();
_container.RegisterType<IDBContext,DbContext>();
var view=_container.Resolve<IView>();
//Create Region http://msdn.microsoft.com/en-us/library/ff648829.aspx
_regionManager.Regions["MainRegion"].Add(view);
}
}
The above will register all of your view, viewmodel and dbcontext, resolve them and add them into a region. For the above to work I'm expecting the following:
public class View:IView
{
public View(IViewModel viewModel)
{
}
}
public class ViewModel:IViewModel
{
public ViewModel(IDbContext context)
{
}
}
Basically, I'm expecting your viewmodel to be injected into your View and your DB Context to be injected into your ViewModel using Constructor Injection.
BTW - the links in the code go to MS sites that will provide more background on Module creation and Regions. I've got one final link: This is a "Hello World" Prism app. It's for Silverlight but this is basically the same thing as a WPF app in terms of code structure so should be useful:Prism Hello World