RaisePropertyChanged doesn't work for ObservableCollection - mvvm

I have a really weird problem with update UI using MVVM Light Toolkit. The RaisePropertyChanged doesn't work at all for my ObservableCollection.
Here is the XAML code:
<ListBox x:Name="list" ItemsSource="{Binding ModelList}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<CheckBox IsChecked="{Binding IsChecked}"></CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<interaction:Interaction.Triggers>
<interaction:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding TempCommand}" CommandParameter="{Binding ElementName=list, Path=SelectedItem}"
PassEventArgsToCommand="True"/>
</interaction:EventTrigger>
</interaction:Interaction.Triggers>
</ListBox>
And there is ViewModel code part:
private Model _selectedItem;
public Model SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
private ObservableCollection<Model> _modelList;
public ObservableCollection<Model> ModelList
{
get { return _modelList; }
set
{
_modelList = value;
RaisePropertyChanged("ModelList");
}
}
public RelayCommand<Model> TempCommand { get; private set; }
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
modelList = new ObservableCollection<Model>()
{
new Model()
{
IsChecked = true,
Name = "Temp1"
},
new Model()
{
IsChecked = false,
Name = "Temp2"
},
new Model()
{
IsChecked = false,
Name = "Temp3"
}
};
ModelList = modelList;
TempCommand = new RelayCommand<Model>(Model_SelectedItem);
}
private void Model_SelectedItem(Model item)
{
// What should I do here?
}
When I change the ModelList - there is no reaction from ListBox UI.
Anyone can help me ?

Problem solved.
When you have custom class in ObservableCollection or List it has to derived from ObservableObject and all the Properties have to fire RaisePropertyChanged event.

Related

Bind ProgressRing to mvvm

I am downloading data from an api, and displaying that data in the view. As I wait I want to display a ProgressRing, but when I bind it dosen't work
Bind the ring to the cityData property, with a two way mode and update it when the property changes
Created a new property in the VM, that is true by default and it will turn false when I get the data back
XAML
<TextBox x:Name="currentLocation"
PlaceholderText="Please wait..."
IsReadOnly="True"
Margin="20"
Width="300"
Text="{Binding cityData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<ListView RelativePanel.Below="currentLocation"
x:Name="ForecastList"
Margin="20"
SelectedItem="{Binding currentDay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding dailyForecasts}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<TextBlock x:Name="dateTB" Text="{Binding Date.DayOfWeek}" />
<TextBlock x:Name="highTB" Text="{Binding Temperature.Maximum.Value, Converter={StaticResource cv}}" FontSize="10" />
<TextBlock x:Name="lowTB" FontSize="10" Text="{Binding Temperature.Minimum.Value, Converter={StaticResource cv}}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ProgressRing x:Name="pRing" RelativePanel.Above="ForecastList" RelativePanel.AlignHorizontalCenterWith="currentLocation" IsActive="{Binding ring, Mode=TwoWay}" RelativePanel.Below="currentLocation" />
VM
public class WeatherVM: INotifyPropertyChanged
{
public AccuWeather accuWeather { get; set; }
private string _cityData;
public string cityData
{
get { return _cityData; }
set
{
if (value != _cityData)
{
_cityData = value;
onPropertyChanged("cityData");
GetWeatherData();
}
}
}
private DailyForecast _currentDay;
public DailyForecast currentDay
{
get { return _currentDay; }
set
{
if (value != _currentDay) \
{
_currentDay = value;
onPropertyChanged("currentDay");
}
}
}
public bool ring { get; set; } = true;
public ObservableCollection<DailyForecast> dailyForecasts { get; set; }
public WeatherVM()
{
GetCuurentLocation();
dailyForecasts = new ObservableCollection<DailyForecast>();
}
private async void GetCuurentLocation() {
cityData = await BingLocator.GetCityData();
}
public async void GetWeatherData() {
var geoposition = await LocationManager.GetGeopositionAsync();
var currentLocationKey = await WeatherAPI.GetCityDstaAsync(geoposition.Coordinate.Point.Position.Latitude, geoposition.Coordinate.Point.Position.Longitude);
var weatherData = await WeatherAPI.GetWeatherAsync(currentLocationKey.Key);
if (weatherData != null) {
foreach (var item in weatherData.DailyForecasts) {
dailyForecasts.Add(item);
}
}
currentDay = dailyForecasts[0];
ring = false;
}
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(string property) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
The progress ring appears when the app launch, and disappear when the data is returned
From your code, it seems to be a layout issue. First you put ListView below currentLocation, then set ProgressRing above ListView and below currentLocation, so the height of ProgressRing wil be zero. I'm not clear about your layout, you can try to only set RelativePanel.Above="ForecastList" for ProgressRing to see if it will appear.
You can use Windows Community Toolkit control for showing progress ring (Busy indicator).
<Page ...
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"/>
<controls:Loading x:Name="LoadingControl" IsLoading="{Binding IsBusy}">
<!-- Loading screen content -->
</controls:Loading>

xamarin form: Custom View in listview on android screw up

I found xamarin render ui is very difficult.
I have a custom view inside the listview which contain a list of small icons. Each item may have some visible icon showing. I realized when i scrolled down to bottom and scroll back to the top, the one supposed to have that icon to be shown was not shown. I passed in an object into the custom view to tell which one to be displayed.
Item in listview, local:ChildInfoIconsView is the custom view.
<StackLayout Orientation="Vertical"
Grid.Row="0"
Grid.Column="1"
VerticalOptions="Center"
HorizontalOptions="Start">
<Label AutomationId="aChildName" Style="{StaticResource MediumBoldFont}" x:Name="childName" Text="{Binding DisplayName}" HorizontalOptions="StartAndExpand" />
<local:ChildInfoIconsView
Child="{Binding .}"
VerticalOptions="Fill">
</local:ChildInfoIconsView>
</StackLayout>
Main Code for custom view
public partial class ChildInfoIconsView : ContentView
{
public static readonly BindableProperty OrientationProperty = BindableProperty.Create(nameof(Orientation), typeof(StackOrientation), typeof(StackOrientation), StackOrientation.Vertical, BindingMode.OneWay, null);
public static readonly BindableProperty ShowClassroomProperty = BindableProperty.Create(nameof(ShowClassroom), typeof(bool), typeof(bool), true, BindingMode.OneWay, null);
public static readonly BindableProperty ChildProperty = BindableProperty.Create(nameof(Child), typeof(ChildModel), typeof(ChildModel), null, BindingMode.OneWay, null);
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName.Equals(nameof(Child)))
{
if (Child != null)
{
this.ImageUnauth.IsVisible = (Child.HasUnauthorized.HasValue) ? Child.HasUnauthorized.Value : false;
this.ImageMedical.IsVisible = (Child.HasMedical.HasValue) ? Child.HasMedical.Value : false;
this.ImageBirthday.IsVisible = Child.IsBirthday;
this.ImageNewChild.IsVisible = !string.IsNullOrEmpty(Child.NewChildFlagImage);
this.ImageClassroom.IsVisible = (!ShowClassroom) ? false : !string.IsNullOrEmpty(Child.Classroom);
this.textClass.Text = Child.Classroom;
this.textClass.IsVisible = (!ShowClassroom) ? false : !string.IsNullOrEmpty(Child.Classroom);
}
}
else if (propertyName.Equals(nameof(ShowClassroom)))
{
this.ImageClassroom.IsVisible = ShowClassroom;
this.textClass.IsVisible = ShowClassroom;
}
else if (propertyName.Equals(nameof(Orientation)))
{
this.layout.Orientation = Orientation;
}
}
public ChildInfoIconsView()
{
InitializeComponent();
}
public StackOrientation Orientation
{
get
{
return (StackOrientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
public bool ShowClassroom
{
get
{
return (bool)GetValue(ShowClassroomProperty);
}
set
{
SetValue(ShowClassroomProperty, value);
}
}
public ChildModel Child
{
get
{
return (ChildModel)GetValue(ChildProperty);
}
set
{
SetValue(ChildProperty, value);
}
}
It work fine when using old version of xamarin.forms. When updated the package, this happen on android.

Master/Details binding MVVM

I have some problem with correct binding my folder with images to master details and other operation with them.
So, I have model of folder and image
public class AppFolder
{
private long id;
private List<AppImage> appImages;
public AppFolder() { }
public List<AppImage> AppImages { get => appImages; set => appImages = value; }
public long Id { get => id; set => id = value; }
}
public class AppImage
{
private int id;
private string title;
private ImageSource appImageURL;
public AppImage() { }
public AppImage(string title, ImageSource imageSource)
{
Title = title;
AppImageURL = imageSource;
}
public int Id { get => id; set => id = value; }
public string Title { get => title; set => title = value; }
public ImageSource AppImageURL { get => appImageURL; set => appImageURL = value; }
}
And I bind List to Master/Details.
public class UserPhotosViewModel : ViewModelBase
{
private readonly IDataService dataService;
private readonly INavigationService navigationService;
public UserPhotosViewModel(IDataService dataService, INavigationService navigationService)
{
this.dataService = dataService;
this.navigationService = navigationService;
Initialize();
}
private async Task Initialize()
{
var item = new List<AppFolder>();
try
{
item = await dataService.GetDataList();
FolderList = item;
}
catch (Exception ex)
{
}
}
private List<AppFolder> folderList;
public List<AppFolder> FolderList
{
get { return folderList; }
set
{
folderList = value;
RaisePropertyChanged(nameof(FolderList));
}
}
}
Example xaml file
<controls:MasterDetailsView ItemsSource="{Binding FolderList}">
<controls:MasterDetailsView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id}"></TextBlock>
</DataTemplate>
</controls:MasterDetailsView.ItemTemplate>
<controls:MasterDetailsView.DetailsTemplate>
<DataTemplate>
<controls:AdaptiveGridView ItemsSource="{Binding AppImages}"
OneRowModeEnabled="False"
ItemHeight="205"
DesiredWidth="205"
SelectionMode="Multiple"
Margin="0 6 0 0"
>
<controls:AdaptiveGridView.ItemTemplate>
<DataTemplate >
<Grid Background="White" Margin="10">
<Image
Source="{Binding AppImageURL}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="Uniform"
/>
<TextBlock Text="{Binding TextTitle}"></TextBlock>
</Grid>
</DataTemplate>
</controls:AdaptiveGridView.ItemTemplate>
</controls:AdaptiveGridView>
</DataTemplate>
</controls:MasterDetailsView.DetailsTemplate>
</controls:MasterDetailsView>
So, it's work correct and I saw my folders with images on page
enter image description here
Look nice and I think it all.
But when I want to add event and SelectedItem to AdaptiveGridView from MVVM model, I saw that it doesn't see them. Visual Studio show me that I could to write them in Model "AppFolder" but it's nonsens....
So, my question: How I can add event (binding command/method) to adaptive grid from UserPhotosViewModel?
Thank you for your time.
UPDATE
enter image description here
2) User double click on image and program send folder with this image and other to page and binding them to FlipView (imitation full screen viewer)
I try to add, but that way also offers to write event and property in model. I use this way for settings in navigation panel (parent control for master/details)
You just need to add command property in your AppFolder class like the following:
public class AppFolder:ViewModelBase
{
private long id;
private List<AppImage> appImages;
public AppFolder() { }
public List<AppImage> AppImages { get => appImages; set => appImages = value; }
public long Id { get => id; set => id = value; }
public RelayCommand<object> relayCommand { get; set; }
}
Then, in your UserPhotosViewModel, you could declare a method for initializing this command.
Since I do not know what the dataService.GetDataList() is. I just change this place in your code and make a simple code sample for you.
private void Initialize()
{
var item = new List<AppFolder>();
try
{
List<AppImage> ls = new List<AppImage>();
ls.Add(new AppImage() { Id = 1, Title = "aaa", AppImageURL = new BitmapImage(new Uri("ms-appx:///Assets/1.jpg")) });
ls.Add(new AppImage() { Id = 2, Title = "bbb", AppImageURL = new BitmapImage(new Uri("ms-appx:///Assets/2.jpg")) });
item.Add(new AppFolder() { Id = 1, AppImages = ls,relayCommand=new RelayCommand<object>(DoubleTapCommand) });
FolderList = item;
}
catch (Exception ex)
{
}
}
private void DoubleTapCommand(object obj)
{
//the obj will be an AppFolder object
}
<controls:MasterDetailsView.DetailsTemplate>
<DataTemplate>
<controls:AdaptiveGridView x:Name="grid" ItemsSource="{Binding AppImages}"
OneRowModeEnabled="False"
ItemHeight="205"
DesiredWidth="205"
SelectionMode="Multiple"
Margin="0 6 0 0">
<Interactivity:Interaction.Behaviors>
<Interactions:EventTriggerBehavior EventName="DoubleTapped">
<Interactions:InvokeCommandAction Command="{Binding relayCommand}" CommandParameter="{Binding}"></Interactions:InvokeCommandAction>
</Interactions:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<controls:AdaptiveGridView.ItemTemplate>
<DataTemplate >
<Grid Background="White" Margin="10">
<Image
Source="{Binding AppImageURL}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="Uniform"
/>
<TextBlock Text="{Binding TextTitle}"></TextBlock>
</Grid>
</DataTemplate>
</controls:AdaptiveGridView.ItemTemplate>
</controls:AdaptiveGridView>
</DataTemplate>
</controls:MasterDetailsView.DetailsTemplate>
1) User select N images and delete them (example)
About this requirement, I suggested that you'd better add a button to bind command for deleting selected items.

Best practice for nesting ContentViews into ContentPage as Xamarin architecture

I am have an app that basically loads a bunch of ContentViews into the home ContentPage. I THINK there is a problem here because all of the viewmodels would essentially need to been initialized every time we load the home page. I am wondering if it is worth my time to ditch the below code and convert the views from ContentView's to ContentPage's and just do Navigation.PushAsync(new View1()); instead. Sorry I know this is alot of example code but I would really like to get a clear picture of best practice.
My Home.xaml
<Grid x:Name="ContentBody" VerticalOptions="FillAndExpand">
<local:View1 Grid.Row="0" x:Name="View1" IsVisible="{Binding View1IsVisible}" BindingContext="{Binding View1ViewModel}" />
<local:View2 Grid.Row="0" x:Name="View2" IsVisible="{Binding View2IsVisible}" BindingContext="{Binding View2ViewModel}" />
<local:View3 Grid.Row="0" x:Name="View3" IsVisible="{Binding View3IsVisible}" BindingContext="{Binding View3ViewModel}" />
<local:View4 Grid.Row="0" x:Name="View4" IsVisible="{Binding View4IsVisible}" BindingContext="{Binding View4ViewModel}" />
<local:View5 Grid.Row="0" x:Name="View5" IsVisible="{Binding View5IsVisible}" BindingContext="{Binding View5ViewModel}" />
<local:View6 Grid.Row="0" x:Name="View6" IsVisible="{Binding View6IsVisible}" BindingContext="{Binding View6ViewModel}" />
<local:DrawerView Grid.Row="0" x:Name="DrawerView" IsVisible="{Binding DrawerViewIsVisible}" />
</Grid>
Then In my HomeViewModel...
private readonly View1ViewModel _view1ViewModel = new View1ViewModel();
public View1ViewModel View1ViewModel { get { return _view1ViewModel; } }
private readonly View2ViewModel _view2ViewModel = new View2ViewModel();
public View2ViewModel View2ViewModel { get { return _view2ViewModel; } }
private readonly View3ViewModel _view3ViewModel = new View3ViewModel();
public View3ViewModel View3ViewModel { get { return _view3ViewModel; } }
private readonly View4ViewModel _view4ViewModel = new View4ViewModel();
public View4ViewModel View4ViewModel { get { return _view4ViewModel; } }
private readonly View5ViewModel _view5ViewModel = new View5ViewModel();
public View5ViewModel View5ViewModel { get { return _view5ViewModel; } }
private readonly View6ViewModel _view6ViewModel = new View6ViewModel();
public View6ViewModel View6ViewModel { get { return _view6ViewModel; } }
///////////////////Some Visibility Properties...//////////////////////
///////////////////Some Visibility Properties...//////////////////////
private bool _view1IsVisible;
public bool View1IsVisible
{
get { return _view1IsVisible; }
set { _view1IsVisible = value; OnPropertyChanged("View1IsVisible"); }
}
private bool _view2IsVisible;
public bool View2IsVisible
{
get { return _view2IsVisible; }
set { _view2IsVisible = value; OnPropertyChanged("View2IsVisible"); }
}
private bool _view3IsVisible;
public bool View3IsVisible
{
get { return _view3IsVisible; }
set { _view3IsVisible = value; OnPropertyChanged("View3IsVisible"); }
}
private bool _view4IsVisible;
public bool View4IsVisible
{
get { return _view4IsVisible; }
set { _view4IsVisible = value; OnPropertyChanged("View4IsVisible"); }
}
private bool _view5IsVisible;
public bool View5IsVisible
{
get { return _view5IsVisible; }
set { _view5IsVisible = value; OnPropertyChanged("View5IsVisible"); }
}
private bool _view6IsVisible;
public bool View6IsVisible
{
get { return _view6IsVisible; }
set { _view6IsVisible = value; OnPropertyChanged("View6IsVisible"); }
}
/////And then this is more or less a method to show the view/////////////
private void ShowView(ViewChangedEventArgs e)
{
HideAllViews();
switch(e.SelectedView){
case ViewType.View1:
View1IsVisible = true
break;
case ViewType.View2:
View2IsVisible = true
break;
case ViewType.View3:
View3IsVisible = true
break;
case ViewType.View4:
View4IsVisible = true
break;
case ViewType.View5:
View5IsVisible = true
break;
case ViewType.View6:
View6IsVisible = true
break;
}
}
Can someone tell me if this approach is fine? as using this approach everytime I add a new page I will need to add a view to the Homepage view and the viewModel & IsVisible properties to the Homepage ViewModel..
I would greatly appreciate any guidance on this. I think a better approach would be to just seperate the ContentViews from the HomePage and when I Navigate to one of these views I would just PushAsync. I have seen supporting documentation online where some people are taking the above approach, I am just trying to ask the experts what they think when they see this code.
After speaking w/ a member of the Xamarin team I've been told to remove all the views from this homeview page, bind the perspective view models in their own code behinds, and from there I can navigation using Navigation.PushAsync(new View1());
Tutorial:
https://github.com/XamarinUniversity/XAM290
Documentaiton:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/

How to change a radio box from true to false using MVVM

So I have a radio button that I want to change from being unchecked to being checked based on if a button is clicked. So
the XAML code I have is:
<RadioButton GroupName="rdoExchange" Grid.Row="2" Grid.Column="0" x:Name="PauseRadioButton" Command="{Binding PauseCommand}" IsChecked="{Binding Path=check, Mode=TwoWay}" Margin="3"/>
And the View Model Code:
public string check = "True";
public void Reset(object obj)
{
check = "True";
}
private ICommand m_PauseCommand;
public ICommand PauseCommand
{
get
{
return m_PauseCommand;
}
set
{
m_PauseCommand = value;
}
}
private ICommand m_ResetCommand;
public ICommand ResetCommand
{
get
{
return m_ResetCommand;
}
set
{
m_ResetCommand = value;
}
}
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string name = "")
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
I've left out the relaycommand code and several other parts that I feel would be irrelevant to solving the problem.
Would something like this work?
XAML:
<RadioButton GroupName="rdoExchange" IsChecked="{Binding Path=OptionOneChecked, Mode=TwoWay}"/>
<Button Command="{Binding ToggleOptionOne}" Height="20" />
View Model:
public class ViewModel: NotificationObject
{
private bool _optionOneChecked;
public bool OptionOneChecked
{
get { return _optionOneChecked; }
set
{
if (value.Equals(_optionOneChecked)) return;
_optionOneChecked = value;
RaisePropertyChanged("OptionOneChecked");
}
}
public ICommand ToggleOptionOne
{
get { return new DelegateCommand(() => OptionOneChecked = !OptionOneChecked); }
}
}
using the NotificationObject class from the PRISM NuGet package.