I am using Prism which helps me in binding my view to the view model. Using INavigationAware, I am hoping to update the Observable collection in my Listview from OnNavigatingTo(). On debugging this method is accessible, however it does not seem to update the ObservableCollection that is bound to the view.
Below is the ViewModel, that is inherited from Prism's BindableBase and INavigationAware:
public class QuoteDetailPageViewModel : BindableBase, INavigationAware
{
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private ObservableCollection<Message> _messages;
private ObservableCollection<Message> Messages
{
get { return _messages; }
set { SetProperty(ref _messages, value); }
}
private Author _selectedAuthor;
private Author SelectedAuthor
{
get { return _selectedAuthor; }
set { SetProperty(ref _selectedAuthor, value); }
}
public QuoteDetailPageViewModel(INavigationService navigationService)
{
Title = "Text Messages";
}
public void OnNavigatingTo(NavigationParameters parameters)
{
var id = -1;
if (parameters != null && parameters.ContainsKey("id"))
{
int.TryParse(parameters["id"].ToString(), out id);
}
if (id > 0)
{
Title = "Contact Message";
}
var msgs = new List<Message>()
{
new Message() {Text = "An investment in knowledge pays the best
interest."},
new Message() {Text = "Early to bed and early to rise makes a
man healthy, wealthy, and wise."},
new Message()
{
Text = "It's fine to celebrate success but it is more
important to heed the lessons of failure."
},
};
Messages = new ObservableCollection<Message>(msgs);
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
}
}
public class Message
{
public string Text { get; set; }
}
And Below is the xaml 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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="PrismAppTutorial.Views.QuoteDetailPage"
Title="{Binding Title}">
<StackLayout Margin="0,20,0,0">
<ListView x:Name="lvAuthorQuotes"
ItemsSource="{Binding Messages}"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Padding="20,10"
Orientation="Vertical"
HorizontalOptions="FillAndExpand"
VerticalOptions="StartAndExpand">
<Label Text="{Binding Text}"
HorizontalOptions="StartAndExpand"
VerticalOptions="StartAndExpand"
LineBreakMode="WordWrap" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
Anyone knows how to fix this?
Turns out I had set the Messages as private and not public. This needs to be public so as to be applied in the view.
Related
This question already has an answer here:
How pass parameter when navigate to another page (Shell)
(1 answer)
Closed 4 months ago.
I have main page on which I have collection view. I want to navigate to next page after I click submit button .I am able to navigate to the next page but I also want total list of items which I have in my collection how can I achieve that?
I don't know the detail of your code, but you can try to pass the total list of items as the parameter of the next page.(suppose it's name is SecondPage)
You can refer to the following code:
MainPage.cs
public partial class MainPage : ContentPage
{
MyViewModel myViewModel;
public MainPage()
{
InitializeComponent();
myViewModel = new MyViewModel();
BindingContext = myViewModel;
}
private void mCollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string previous = (e.PreviousSelection.FirstOrDefault() as MyModel)?.Name;
string current = (e.CurrentSelection.FirstOrDefault() as MyModel)?.Name;
}
private async void Button_Clicked(object sender, EventArgs e)
{
// here we can pass the data we need.
var secondPage = new SecondPage(myViewModel.Data);
await Navigation.PushAsync(secondPage);
}
}
MainPage.xaml.cs
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiCollectionApp.MainPage"
xmlns:local="clr-namespace:MauiCollectionApp"
x:Name="myPage"
>
<VerticalStackLayout>
<CollectionView ItemsSource="{ Binding Data}" x:Name="mCollectionView"
SelectionChanged="mCollectionView_SelectionChanged"
SelectionMode="Single"
>
<CollectionView.ItemTemplate>
<DataTemplate>
<HorizontalStackLayout Margin="3" >
<Label Text="{Binding Name}" BackgroundColor="Gray"/>
<Label Text="{Binding Car.Make}" Margin="5,0,5,0" />
<Button Text="delete" Margin="10,0,0,0"
BackgroundColor="Red"
Command="{Binding Path= BindingContext.RemoveEquipmentCommand,Source={Reference mCollectionView }}" CommandParameter="{Binding .}"
/>
</HorizontalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Button Text="submit" Clicked="Button_Clicked" Margin="10"/>
</VerticalStackLayout>
</ContentPage>
MyViewModel.cs
public class MyViewModel: INotifyPropertyChanged
{
public ObservableCollection<MyModel> Data { get; set; }
public ICommand RemoveEquipmentCommand => new Command<MyModel>(ReMoveItem);
private void ReMoveItem(MyModel obj)
{
System.Diagnostics.Debug.WriteLine(" the selected item's name is: " + obj.Name );
Data.Remove(obj);
}
public MyViewModel() {
Data = new ObservableCollection<MyModel>();
Data.Add(new MyModel { Name ="model_1", Car= new Vehicle {Make="Make1" } });
Data.Add(new MyModel { Name = "model_2", Car = new Vehicle { Make = "Make2" } });
Data.Add(new MyModel { Name = "model_3", Car = new Vehicle { Make = "Make3" } });
Data.Add(new MyModel { Name = "model_4", Car = new Vehicle { Make = "Make4" } });
}
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
SecondPage.cs
public partial class SecondPage : ContentPage
{
public ObservableCollection<MyModel> Items { get; set; }
public SecondPage(ObservableCollection<MyModel> data )
{ // we can get the passed data here
InitializeComponent();
this.Items = data;
}
}
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();
}
}
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.
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>
I have created Custom User Control which contain TextBox and PasswordBox. it is binding completely work but when i changed any value inside TextBox or PasswordBox of user control then my source property does not getting refreshed.
Following are the code for my Custom User Control
RestrictedBox.xaml
<UserControl.Resources>
<Converters:EnumToVisibilityConverter x:Key="enumToVisibilityConverter" />
<Converters:EnumToVisibilityConverterReverse x:Key="enumToVisibilityConverterReverse" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="Transparent" >
<StackPanel>
<TextBox x:Name="txtTextBox" Width="50" Height="25" />
<PasswordBox x:Name="txtPasswordBox" Width="50" Height="25" />
</StackPanel>
</Grid>
RestrictedBox.xaml.cs
public partial class RestrictedBox : UserControl
{
public RestrictedBox()
{
InitializeComponent();
txtTextBox.SetBinding(TextBox.TextProperty, new Binding { Source = this, Path = new PropertyPath("Value"), Mode = BindingMode.TwoWay });
txtTextBox.SetBinding(TextBox.VisibilityProperty, new Binding("Type")
{
Source = this,
Converter = new EnumToVisibilityConverter()
});
txtPasswordBox.SetBinding(PasswordBox.PasswordProperty, new Binding { Source = this, Path = new PropertyPath("Value"), Mode = BindingMode.TwoWay });
txtPasswordBox.SetBinding(TextBox.VisibilityProperty, new Binding("Type")
{
Source = this,
Converter = new EnumToVisibilityConverterReverse()
});
}
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(string), typeof(RestrictedBox), new PropertyMetadata("", ValueChanged));
private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public Mode Type
{
get { return (Mode)GetValue(TypeProperty); }
set { SetValue(TypeProperty, value); }
}
public static readonly DependencyProperty TypeProperty = DependencyProperty.Register("Type", typeof(Mode), typeof(RestrictedBox), new PropertyMetadata(Mode.Text, TypeChanged));
private static void TypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}
Following are the code for LoginView where i have used my custom User Control (RestrictedBox).
LoginView.xaml
<control:RestrictedBox Type="Text" Value="{Binding Path=UserName}" />
LoginView.xaml.cs
[Export(typeof(LoginView))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class LoginView : UserControl, IPageTitle
{
#region Constuctors
public LoginView()
{
InitializeComponent();
}
[Import]
public LoginViewModel ViewModel
{
get
{
return this.DataContext as LoginViewModel;
}
set
{
DataContext = value;
}
}
#endregion
}
LoginViewModel.cs
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class LoginViewModel : INotifyPropertyChanged, IRegionMemberLifetime
{
private string _UserName = "";
public string UserName
{
get { return _UserName; }
set
{
_UserName = value;
OnPropertyChanged("UserName");
}
}
[ImportingConstructor]
public LoginViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
{
}
}
Please help me to resolved this because i am trying to resolve since last 1.5 days without any luck.
Your comments and suggestions would be highly appreciated.
Note:- I am able to bind value of the UserName to TextBox but i update TextBox and click on submit i couldn't getting updated value from TextBox.
Thanks,
Imdadhusen
You are missing Mode=TwoWay in you LoginView.xaml:
<control:RestrictedBox Type="Text" Value="{Binding Path=UserName,Mode=TwoWay}" />