I have a problem. What I am trying to do is bind an ImageSource from a ViewModel. The image file name is called ledstrip.png and is located in the Resources/Drawable folder. I am using the following xaml:
<ListView ItemsSource="{Binding knownDeviceList}" SelectionMode="None" RowHeight="90">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<AbsoluteLayout HeightRequest="70" Margin="20,20,20,0">
<StackLayout Opacity="0.3" BackgroundColor="White"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All" />
<StackLayout AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All">
<Grid RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="70" />
</Grid.ColumnDefinitions>
<Image Source="{Binding DeviceImage}" Grid.RowSpan="2" Grid.Column="0" Margin="5" />
</Grid>
</StackLayout>
</AbsoluteLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And in my ViewModel I have the following code:
public class VM_DeviceList : BindableObject
{
private ObservableCollection<DisplayedDevice> _knownDeviceList;
public ObservableCollection<DisplayedDevice> knownDeviceList
{
get
{
return _knownDeviceList;
}
set
{
if (_knownDeviceList != value)
{
_knownDeviceList = value;
OnPropertyChanged();
}
}
}
public VM_DeviceList()
{
knownDeviceList = new ObservableCollection<DisplayedDevice>();
MyHandler();
}
private async Task LoadKnownDeviceList()
{
foreach (KnownDevice device in App.KnownDeviceList)
{
DisplayedDevice displayedDevice = new DisplayedDevice();
displayedDevice.Id = device.Id;
displayedDevice.Name = device.Name;
switch (device.Type)
{
case "ledstrip":
displayedDevice.DeviceImage = "ledstrip.png";
break;
case "triangle":
displayedDevice.DeviceImage = "triangle.png";
break;
}
knownDeviceList.Add(displayedDevice);
}
}
public Task MyHandler()
{
return LoadKnownDeviceList();
}
public class DisplayedDevice
{
public int Id { get; set; }
public string Name { get; set; }
public string DeviceImage { get; set; }
}
}
The problem is that when I type "ledstrip.png" in the xaml ImageSource, the image gets displayed, but when I bind it like the way I show above, no image appears on the screen!
What am I doing wrong and how can I fix this?
Since you are altering the DisplayImage on a separate for loop. You have to Notify the UI that the DisplayImage property value has been changed.
Use INotifyPropertyChanged for notifying that DisplayImage property of DisplayedDevice class has been changed to the UI.
public class DisplayedDevice : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string deviceImage;
public int Id { get; set; }
public string Name { get; set; }
public string DeviceImage
{
get
{
return deviceImage;
}
set
{
deviceImage = value;
OnPropertyChanged();
}
}
}
Related
i have a CollectionView and my custom buttom. I want to make a grid with buttons.When I click on button it change a background color.I want to write in void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e) something AND that the text of label is Name(class field) of SELECTED button.When I click on buttons in collectionview it change color but button is not clickable,it does not see it,if I write image it can read data.Please help me make button clickable
<StackLayout>
<Label x:Name="meow1"></Label>
<CollectionView ItemsSource="{Binding Cars}" x:Name="phonesList"
HeightRequest="90"
ItemsLayout="HorizontalList"
BackgroundColor="Transparent"
SelectionMode="Single"
SelectionChanged="OnCollectionViewSelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame x:Name="frame" CornerRadius="10" BackgroundColor="Black" Padding="0" HeightRequest="90"
WidthRequest="95">
<Grid Padding="0" x:Name="meow">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<controls:CustomButton TintColor="#725762" HeightRequest="90"
WidthRequest="90" CornerRadius="10" HorizontalOptions="Center"
BackgroundColor="White" ImageSource="{Binding ImagePath}" Clicked="Button_OnClicked"/>
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
meow1.Text = (e.CurrentSelection.FirstOrDefault() as Car).NameImage;
}
Although not too much understanding the problem , but there is a suggestion about Button click event in CollectionView. We will use Command and CommandParameter of Button when binding model . And that is the design idea of MVVM.
For example , the Xaml code modeified as follow:
<StackLayout>
<Label x:Name="meow1"
Text="{Binding SelectedCarItem.NameImage}"
FontSize="Large"
VerticalOptions="Start"
HorizontalOptions="CenterAndExpand" />
<CollectionView ItemsSource="{Binding Cars}"
x:Name="phonesList"
HeightRequest="90"
ItemsLayout="HorizontalList"
BackgroundColor="Transparent"
SelectionMode="Single"
SelectedItem="{Binding SelectedCarItem}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame x:Name="frame"
CornerRadius="10"
BackgroundColor="{Binding BgFrameColor}"
Padding="0"
HeightRequest="90"
WidthRequest="95">
<Grid Padding="0"
x:Name="meow">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
HeightRequest="90"
WidthRequest="90"
CornerRadius="10"
HorizontalOptions="Center"
BackgroundColor="{Binding BgButtonColor}"
ImageSource="{Binding ImagePath}"
Command="{Binding TapCommand}"
CommandParameter="{Binding Source={x:Reference frame}, Path=BindingContext}" />
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
Then need to modify the Car model , adding BgColor,IsSelected and TapCommand property:
public class Car : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string NameImage { get; set; }
public string ImagePath { get; set; }
private Color bgFrameColor;
public Color BgFrameColor
{
set
{
if (bgFrameColor != value)
{
bgFrameColor = value;
OnPropertyChanged("BgFrameColor");
}
}
get
{
return bgFrameColor;
}
}
private Color bgButtonColor;
public Color BgButtonColor
{
set
{
if (bgButtonColor != value)
{
bgButtonColor = value;
OnPropertyChanged("BgButtonColor");
}
}
get
{
return bgButtonColor;
}
}
private bool isSelected;
public bool IsSelected
{
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
get
{
return isSelected;
}
}
public ICommand TapCommand
{
get
{
return new Command((e) =>
{
var item = (e as Car);
// logic on item
if (item.isSelected)
{
item.isSelected = false;
item.BgButtonColor = Color.White;
item.BgFrameColor = Color.Black;
PageCollectionView.SelectedCar.Remove(item);
MessagingCenter.Send<object, Car>(this, "Hi", new Car() {NameImage ="Welcome to the car home!" });
}
else
{
item.isSelected = true;
item.BgButtonColor = Color.Blue;
item.BgFrameColor = Color.Yellow;
if (PageCollectionView.SelectedCar.Count == 0)
{
PageCollectionView.SelectedCar.Add(item);
}
else
{
PageCollectionView.SelectedCar[0].isSelected = false;
PageCollectionView.SelectedCar[0].BgButtonColor = Color.White;
PageCollectionView.SelectedCar[0].BgFrameColor = Color.Black;
PageCollectionView.SelectedCar.Remove(PageCollectionView.SelectedCar[0]);
PageCollectionView.SelectedCar.Add(item);
}
MessagingCenter.Send<object, Car>(this, "Hi", item);
}
});
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Add CarModel class to load data :
public class CarModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public List<Car> Cars { get; set; }
//public static Car SelectedCarItem { set; get; }
public CarModel()
{
Cars = new List<Car>();
Cars.Add(new Car() { NameImage = "Lexus", ImagePath = "Lexus.png", BgButtonColor = Color.White, BgFrameColor = Color.Black, IsSelected = false }); ;
Cars.Add(new Car { NameImage = "Audi", ImagePath = "Audi.png", BgButtonColor = Color.White, BgFrameColor = Color.Black, IsSelected = false });
// set default text of label
selectedCarItem = new Car() { NameImage = "Welcome to the car home!" };
}
private Car selectedCarItem;
public Car SelectedCarItem
{
get
{
return selectedCarItem;
}
set
{
if (selectedCarItem != value)
{
selectedCarItem = value;
OnPropertyChanged("SelectedCarItem");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in ContentPage , declare a List<Car> to store only one item to keep this collection view is single selected. And use MessagingCenter to update carModel.SelectedCarItem here:
public partial class PageCollectionView : ContentPage
{
public static List<Car> SelectedCar { get; set; }
public PageCollectionView()
{
InitializeComponent();
CarModel carModel = new CarModel();
BindingContext = carModel;
SelectedCar = new List<Car>();
MessagingCenter.Subscribe<object,Car>(this, "Hi", (sender,arg) =>
{
// Do something whenever the "Hi" message is received
carModel.SelectedCarItem = arg;
});
}
}
The effect as follow :
Note: From the sample , you will see that using binding to modify BackgroundColor and Model Data. Therefore, it's not recommanded to use OnCollectionViewSelectionChanged to modify text of Lable .
I have already created all of the controls, and have tested that the controls are added as needed without using the binding.
The issue is as soon as I add the binding which is supposed to determine which control to add, the controls stop working as needed.
The list view control will be populated from a collection of a class, which will have an indicator field to determine which control needs to be loaded. The list view contains a 2nd user control which basically acts as a placeholder for the correct control, it has a bindable property of type text which is set to determine the correct control to be loaded.
Here is the XAML Code for the list view control
<ContentView.Content>
<StackLayout>
<Label Text="Binding Control Type"/>
<Entry x:Name="cntName"/>
<ListView x:Name="GroupedView" GroupDisplayBinding="{Binding Title}" HasUnevenRows="True" GroupShortNameBinding="{Binding ShortName}" IsGroupingEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Add Comment"/>
<MenuItem Text="Add Attachment"/>
</ViewCell.ContextActions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="7*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="7*"/>
</Grid.RowDefinitions>
<Label Text="{Binding QUESTION_ID}" Grid.Row="0" Grid.Column="0" VerticalTextAlignment="Center" FontSize="Medium"/>
<Label Text="{Binding QUESTION_DETAILS}" Grid.Row="1" Grid.Column="0" VerticalTextAlignment="Center" FontSize="Medium"/>
<con:ucListViewControls ControlType="{Binding QUESTION_ANSWERCONTROL}" Grid.Row="1" Grid.Column="1"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Add Comment"/>
<MenuItem Text="Add Attachment"/>
</ViewCell.ContextActions>
<StackLayout Orientation="Horizontal" Padding="5,5,5,5" BackgroundColor="#E2F5F9">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference this}, Path=Tapped}" CommandParameter="{Binding .}"/>
</StackLayout.GestureRecognizers>
<Button Image="{Binding StateIcon}" BackgroundColor="Transparent" BorderColor="Transparent" BorderWidth="0"/>
<Label Text="{Binding Title}" TextColor="#005569" FontSize="15" VerticalOptions="Center"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
</ListView>
</StackLayout>
</ContentView.Content>
And the code behind for the control
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ucExpandibleListView : ContentView
{
private ObservableCollection<dbQuestionGroup> _allGroups;
private ObservableCollection<dbQuestionGroup> _expandedGroups;
public ucExpandibleListView()
{
InitializeComponent();
Tapped = new Command(x => HeaderTapped(x));
_allGroups = new ObservableCollection<dbQuestionGroup>()
{
new dbQuestionGroup("Category 1", "C1", false)
{
new dbQuestionModel() { QUESTION_ID = 1, QUESTION_DETAILS = "Testing Question 1", QUESTION_ANSWERCONTROL = "RBL" },
new dbQuestionModel() { QUESTION_ID = 2, QUESTION_DETAILS = "Testing Question 2", QUESTION_ANSWERCONTROL = "" }
}
};
UpdateListContent();
}
private void UpdateListContent()
{
_expandedGroups = new ObservableCollection<dbQuestionGroup>();
foreach (dbQuestionGroup group in _allGroups)
{
dbQuestionGroup newGroup = new dbQuestionGroup(group.Title, group.ShortName, group.Expanded);
newGroup.QuestionCount = group.Count;
if (group.Expanded)
{
foreach (dbQuestionModel question in group)
{
newGroup.Add(question);
}
}
_expandedGroups.Add(newGroup);
}
GroupedView.ItemsSource = _expandedGroups;
}
public Command Tapped { get; set; }
private void HeaderTapped(object group)
{
var groupCat = (dbQuestionGroup)group;
int selectedIndex = _expandedGroups.IndexOf(groupCat);
if (groupCat.Expanded)
{
_allGroups[selectedIndex].Expanded = false;
}
else
{
_allGroups.ToList().ForEach(x => x.Expanded = false);
_allGroups[selectedIndex].Expanded = !_allGroups[selectedIndex].Expanded;
}
UpdateListContent();
}
}
Here is the XAML Code for the placeholder control
<ContentView.Content>
<StackLayout x:Name="stkPlaceholder">
</StackLayout>
</ContentView.Content>
And the code behind for the placeholder control
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ucListViewControls : ContentView, INotifyPropertyChanged
{
public ucListViewControls()
{
InitializeComponent();
}
#region Control Attributes
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
#endregion
#region Bindable Properties
public static readonly BindableProperty ControlTypeProperty = BindableProperty.Create(nameof(ControlType), typeof(string), typeof(ucListViewControls));
public string ControlType
{
get
{
return (string)GetValue(ControlTypeProperty);
}
set
{
SetValue(ControlTypeProperty, value);
AddControl();
NotifyPropertyChanged("ControlType");
}
}
#endregion
public void AddControl()
{
switch (ControlType)
{
case "RBL":
ucRadiobuttons radiobuttons = new ucRadiobuttons();
radiobuttons.lblTitle1 = "Yes";
radiobuttons.lblTitle2 = "No";
radiobuttons.lblTitle3 = "N/A";
radiobuttons.OnColor1 = Color.Green;
radiobuttons.OnColor2 = Color.Red;
radiobuttons.OnColor3 = Color.Transparent;
stkPlaceholder.Children.Add(radiobuttons);
break;
default:
Entry placeholder = new Entry();
stkPlaceholder.Children.Add(placeholder);
break;
}
}
}
I have tested that the controls are added without the binding, which works perfectly.
I have tried to rewrite the bindable property multiple times in case I missed something, I also could not find any post relating to something similar which wouldve helped me.
Any clues?
The first screenshot shows the expected output, and the seconds screenshots shows what happens when the binding is applied.
dbQuestionModel:
using System.Collections.Generic;
namespace PivotMobile_BusinessLayer.Models
{
public class dbQuestionModel
{
public int QUESTION_PK { get; set; }
public int QUESTION_ID { get; set; }
public string QUESTION_CATEGORY { get; set; }
public string QUESTION_DETAILS { get; set; }
public string QUESTION_TYPE { get; set; }
public string QUESTION_ANSWERCONTROL { get; set; }
public string QUESTION_COMMENT { get; set; }
public List<string> QUESTION_ATTACHMENTS { get; set; }
}
}
dbQuestionGroup:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace PivotMobile_BusinessLayer.Models
{
public class dbQuestionGroup : ObservableCollection<dbQuestionModel>, INotifyPropertyChanged
{
public static ObservableCollection<dbQuestionGroup> All { private set; get; }
private bool _expanded;
public string Title { get; set; }
public string ShortName { get; set; }
public bool Expanded
{
get
{
return _expanded;
}
set
{
if (_expanded != value)
{
_expanded = value;
OnPropertyChanged("Expanded");
OnPropertyChanged("StateIcon");
}
}
}
public string StateIcon
{
get
{
return Expanded ? "expanded_blue.png" : "collapsed_blue.png";
}
}
public int QuestionCount { get; set; }
public dbQuestionGroup(string title, string shortName, bool expanded = true)
{
Title = title;
ShortName = shortName;
Expanded = expanded;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ListView Page 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"
x:Class="PivotMobile.Views.ObservationsView"
xmlns:con="clr-namespace:PivotMobile.Controls">
<ContentPage.Content>
<StackLayout Margin="5">
<con:ucExpandibleListView/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
ListView Page Code Behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace PivotMobile.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ObservationsView : ContentPage
{
public ObservationsView ()
{
InitializeComponent ();
}
}
}
I simply changed my ListView to use grouping, but now I can't use ScrollTo anymore.
I have create a simple app, so you can see the problem.
The XAML-page looks like (I am not using XAML in my app at the moment, but I will in an upcoming version).
<?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:local="clr-namespace:ScrollListExample"
x:Class="ScrollListExample.ProjectPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListView x:Name="ProjectsListView" HasUnevenRows="True" IsGroupingEnabled="True" ItemsSource="{Binding Projects}">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Path=Key}" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" LineBreakMode="TailTruncation" Text="{Binding Path=ProjectName}" />
<Label Grid.Column="0" Grid.Row="1" Text="{Binding Path=ProjectReference, StringFormat='Sag: {0}'}" />
<Label Grid.Column="0" Grid.Row="2" Text="{Binding Path=CustomerName}" />
<Label Grid.Column="0" Grid.Row="3" Text="{Binding Path=FullAddress}" />
<Label Grid.Column="1" Grid.Row="0" Text="{Binding Path=StartTime}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage>
And the code-behind file for the example looks like this
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ProjectPage : ContentPage
{
public ProjectPage()
{
InitializeComponent();
BindingContext = new ProjectsViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
Acr.UserDialogs.UserDialogs.Instance.ShowLoading();
var projects = Newtonsoft.Json.JsonConvert.DeserializeObject<IList<ProjectDto>>("[{\"ProjectName\":\"Test sag\",\"ProjectReference\":\"10072\",\"CustomerName\":\"Test firma\",\"FullAddress\":\"Testvej 3\",\"StartDate\":\"2017-02-02T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"aaa\",\"ProjectReference\":\"10077\",\"CustomerName\":\"Test firma\",\"FullAddress\":\"Testvej 12\",\"StartDate\":\"2017-02-08T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10082\",\"CustomerName\":\"Test firma\",\"FullAddress\":\"Testvej 50\",\"StartDate\":\"2017-02-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10085\",\"CustomerName\":\"Testvej boligselskab\",\"FullAddress\":\"Testvej 14\",\"StartDate\":\"2017-02-24T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10086\",\"CustomerName\":\"Testing\",\"FullAddress\":\"Testevej 14\",\"StartDate\":\"2017-02-27T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test1\",\"ProjectReference\":\"10087\",\"CustomerName\":\"Plejecenter testlyst\",\"FullAddress\":\"Testlystvej 11\",\"StartDate\":\"2017-02-27T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test2\",\"ProjectReference\":\"10088\",\"CustomerName\":\"Charlie\",\"FullAddress\":\"Testvej 50\",\"StartDate\":\"2017-02-27T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10089\",\"CustomerName\":\"Standard Debitor\",\"FullAddress\":\"[Mangler]\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10090\",\"CustomerName\":\"Standard Debitor\",\"FullAddress\":\"[Mangler]\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10091\",\"CustomerName\":\"Standard Debitor\",\"FullAddress\":\"[Mangler]\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10092\",\"CustomerName\":\"Tester\",\"FullAddress\":\"Testvej 11\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10093\",\"CustomerName\":\"Plejehjemmet test\",\"FullAddress\":\"Testvej 90\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10094\",\"CustomerName\":\"Plejehjemmet test\",\"FullAddress\":\"Testvej 90\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"}]");
var viewModel = BindingContext as ProjectsViewModel;
if (viewModel != null)
viewModel.OriginalProjects = projects;
Acr.UserDialogs.UserDialogs.Instance.ShowLoading("Loading");
Task.Delay(5000).ContinueWith((x) =>
{
Device.BeginInvokeOnMainThread(Acr.UserDialogs.UserDialogs.Instance.HideLoading);
Search();
});
}
private void Search(string inputVal = null)
{
var viewModel = BindingContext as ProjectsViewModel;
if (viewModel != null)
{
var projects = viewModel.OriginalProjects.Where(p => !string.IsNullOrEmpty(inputVal) ? p.ProjectName.Contains(inputVal) : true);
var orderedProjects = projects.OrderBy(p => p.StartDate);
Device.BeginInvokeOnMainThread(() =>
{
foreach (ProjectDto project in orderedProjects)
{
var coll = viewModel.Projects.FirstOrDefault(c => c.Key == project.StartDate);
if (coll == null)
viewModel.Projects.Add(coll = new ObservableCollectionWithDateKey { Key = project.StartDate });
coll.Add(project);
}
var group = viewModel.Projects.LastOrDefault();
if (group != null)
ProjectsListView.ScrollTo(group.First(), group.Key, ScrollToPosition.Start, false);
});
}
}
}
class ProjectsViewModel : INotifyPropertyChanged
{
private ObservableCollection<ObservableCollectionWithDateKey> _projects;
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable<ProjectDto> OriginalProjects { get; set; }
public ObservableCollection<ObservableCollectionWithDateKey> Projects
{
get { return _projects; }
set
{
_projects = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Projects)));
}
}
public ProjectsViewModel()
{
Projects = new ObservableCollection<ObservableCollectionWithDateKey>();
}
}
public class ProjectDto : INotifyPropertyChanged
{
public string ProjectName { get; set; }
public string ProjectReference { get; set; }
public string CustomerName { get; set; }
public string FullAddress { get; set; }
public DateTime StartDate { get; set; }
public string StartTime { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
class ObservableCollectionWithDateKey : ObservableCollection<ProjectDto>
{
public DateTime Key { get; set; }
}
I use Task.Delay(5000) to simulate a response from the server, but I do not think, it matters.
UPDATE
I figured out, the problem was in my ScrollTo-call, where ScrollTo(group.First(), group.Key, ScrollToPosition.Start, false); was called with the Key instead of just the group.
If you create the grouping first (without adding it to the ViewModel), you have to find the correct model in the ViewModel afterwards. As it otherwise does not find the correct ObservableCollection
I have tested your code and reproduced your issue. The problem is you have passed the wrong parameter to ScrollTo method.
ProjectsListView.ScrollTo(group.First(), group.Key, ScrollToPosition.Start, false);
The group parameter of ScrollTo method is the group from your ListView.ItemsSource. But your passed a group.Key. So the method will not be excited as expect. Please modify the code like following.
Device.BeginInvokeOnMainThread(() =>
{
foreach (ProjectDto project in orderedProjects)
{
var coll = viewModel.Projects.FirstOrDefault(c => c.Key == project.StartDate);
if (coll == null)
viewModel.Projects.Add(coll = new ObservableCollectionWithDateKey { Key = project.StartDate });
coll.Add(project);
}
var group = viewModel.Projects.Last();
if (group != null)
ProjectsListView.ScrollTo(group.First(), group, ScrollToPosition.Start, false);
});
I'm currently making an app using Xamarin Forms. This app will first call a REST service to retrieve the data and display them then store those data into a SQLite Database. I have an update button where if I click on it, it will prompt the REST service once again to retrieve newer data and replace the old data while the app is running. I have tried to implement the INotifyPropertyChanged but the value just wont' change for me. Am I missing anything with my code below? Thanks!
Vitals Object:
public class Vitals
{
public string Height { get; set; }
public string ID { get; set; }
public string Weight { get; set; }
}
Update Method:
async void OnUpdate(object sender, EventArgs e)
{
string tempUser = globalPatient.Username;
string tempPin = globalPatient.PIN;
patUpdate = patientManager.GetPatientByUsername (tempUser, tempPin).Result;
App.PatientDB.DeletePatient(tempID);
App.PatientDB.AddNewPatient (patUpdate, tempPin);
DisplayAlert ("Updated", "Your information has been updated!", "OK");
}
VitalsViewModal:
public class VitalsViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public VitalsViewModel (Patient patient)
{
vitals = patient.Vitals;
}
private List<Vitals> _vitals;
public List<Vitals> vitals {
get {return _vitals; }
set {
if (_vitals != value) {
_vitals = value;
OnPropertyChanged ("vitals");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
VitalsView
public partial class VitalsView : ContentPage, INotifyPropertyChanged
{
PatientManager patientManager = new PatientManager ();
Patient globalPatient;
public event PropertyChangedEventHandler PropertyChanged;
public VitalsView (Patient patientZero)
{
InitializeComponent ();
BindingContext = new VitalsViewModel (patientZero);
}
}
Xaml
<ListView x:Name="Vitals" ItemsSource="{Binding vitals}" RowHeight="80" BackgroundColor="Transparent">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Vertical" Spacing="0" Padding="15">
<Grid>
<Label Font="17" Text="{Binding Height} " FontAttributes="Bold" TextColor="#449BC4" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" />
<Label Font="14" Text="{Binding Weight, StringFormat='Weight: {0}'}" FontAttributes="Bold" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" />
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
</Grid>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
For vitals to have a change in Xaml, something must replace the whole list, of List<vitals with a new list.
Even though the patient changed and its vitals are new from the update, you have bound to an orphaned patient.vitals whose patient reference is still valid. Hence no change.
You need to specifically change the reference of vitals away from the old one to the new one.
I suggest this:
async void OnUpdate(object sender, EventArgs e)
{
string tempUser = globalPatient.Username;
string tempPin = globalPatient.PIN;
patUpdate = patientManager.GetPatientByUsername (tempUser, tempPin).Result;
App.PatientDB.DeletePatient(tempID);
App.PatientDB.AddNewPatient (patUpdate, tempPin);
MyCurrenViewModel.vitals = patUpdate.Vitals; // Replace old vitals
DisplayAlert ("Updated", "Your information has been updated!", "OK");
}
Note In the above example I would create a property named MyCurrentViewModel on the page, and when assigning the datacontext I would have
public partial class VitalsView : ContentPage, INotifyPropertyChanged
{
VitalsViewModel MyCurrentViewModel { get; set; }
PatientManager patientManager = new PatientManager ();
PatientDemo globalPatient;
public event PropertyChangedEventHandler PropertyChanged;
public VitalsView (Patient patientZero)
{
InitializeComponent ();
//BindingContext = new VitalsViewModel (patientZero);
BindingContext = MyCurrentViewModel = new VitalsViewModel (patientZero);
}
}
Code Review Other Errors
OnUpdate is async which is great, but it never awaits any method call; hence making all calls to it synchronous in nature and blocking the gui thread waiting on results. Never block a gui thread, the app will appear to freeze.
As an option, you can use ObservableCollection instead of List.
I've been working on this problem for about 3 hours now, and I got to a dead end.
Currently I'm trying to bind a list to a ComboBox.
I have used several methods to bind the List:
Code behind:
public partial class MainWindow : Window
{
public coImportReader ir { get; set; }
public MainWindow()
{
ir = new coImportReader();
InitializeComponent();
}
private void PremadeSearchPoints(coSearchPoint sp)
{
//SearchRefPoint.DataContext = ir.SearchPointCollection;
SearchRefPoint.ItemsSource = ir.SearchPointCollection;
SearchRefPoint.DisplayMemberPath = Name;
The data was binded correctly but the DisplayMemeberPath for some reason returned the name of the class and not the name of it's member.
The XAML method returned an empty string...
<ComboBox x:Name="SearchRefPoint" Height="30" Width="324" Margin="0,10,0,0"
VerticalAlignment="Top" ItemsSource="{Binding ir.SearchPointCollection}"
DisplayMemberPath="Name">
I've also tried to fill it with a new list which I create in the MainWindow. the result was the same in both cases.
Also I've tried to create and ListCollectionView which was success, but the problem was that I could get the index of the ComboBox item. I prefer to work by an Id. For that reason I was looking for a new solution which I found at: http://zamjad.wordpress.com/2012/08/15/multi-columns-combo-box/
The problem with this example is that is not clear how the itemsource is being binded.
Edit:
To sum things up: I'm currently trying to bind a list(SearchPointsCollection) of objects(coSearchPoints) defined in a class (coImportReader).
namespace Import_Rates_Manager
{
public partial class MainWindow : Window
{
public coImportReader ir;
public coViewerControles vc;
public coSearchPoint sp;
public MainWindow()
{
InitializeComponent();
ir = new coImportReader();
vc = new coViewerControles();
sp = new coSearchPoint();
SearchRefPoint.DataContext = ir;
}
}
}
//in function....
SearchRefPoint.ItemsSource = ir.SearchPointCollection;
SearchRefPoint.DisplayMemberPath = "Name";
namespace Import_Rates_Manager
{
public class coImportReader
{
public List<coSearchPoint> SearchPointCollection = new List<coSearchPoint>();
}
}
namespace Import_Rates_Manager
{
public class coSearchPoint
{
public coSearchPoint()
{
string Name = "";
Guid Id = Guid.NewGuid();
IRange FoundCell = null;
}
}
}
This results in a filled combobox with no text
Here a simple example using the MVVM Pattern
XAML
<Window x:Class="Binding_a_List_to_a_ComboBox.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">
<Grid HorizontalAlignment="Left"
VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<ComboBox Grid.Column="0" Grid.Row="0" ItemsSource="{Binding SearchPointCollection , UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding MySelectedIndex, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding MySelectedItem, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Id}" Grid.Row="0"/>
<TextBlock Text="{Binding Name}" Grid.Row="1"/>
<TextBlock Text="{Binding Otherstuff}" Grid.Row="2"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Bind NOW" Grid.Column="0" Grid.Row="1" Click="Button_Click"/>
<TextBlock Text="{Binding MySelectedIndex, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="0"/>
<Grid Grid.Column="1" Grid.Row="1"
DataContext="{Binding MySelectedItem, UpdateSourceTrigger=PropertyChanged}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Id}" Grid.Column="0"/>
<TextBlock Text="{Binding Name}" Grid.Column="1"/>
<TextBlock Text="{Binding SomeValue}" Grid.Column="2"/>
</Grid>
</Grid>
Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using Import_Rates_Manager;
namespace Binding_a_List_to_a_ComboBox
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DataContext = new coImportReader();
}
}
}
namespace Import_Rates_Manager
{
public class coImportReader : INotifyPropertyChanged
{
private List<coSearchPoint> myItemsSource;
private int mySelectedIndex;
private coSearchPoint mySelectedItem;
public List<coSearchPoint> SearchPointCollection
{
get { return myItemsSource; }
set
{
myItemsSource = value;
OnPropertyChanged("SearchPointCollection ");
}
}
public int MySelectedIndex
{
get { return mySelectedIndex; }
set
{
mySelectedIndex = value;
OnPropertyChanged("MySelectedIndex");
}
}
public coSearchPoint MySelectedItem
{
get { return mySelectedItem; }
set { mySelectedItem = value;
OnPropertyChanged("MySelectedItem");
}
}
#region cTor
public coImportReader()
{
myItemsSource = new List<coSearchPoint>();
myItemsSource.Add(new coSearchPoint { Name = "Name1" });
myItemsSource.Add(new coSearchPoint { Name = "Name2" });
myItemsSource.Add(new coSearchPoint { Name = "Name3" });
myItemsSource.Add(new coSearchPoint { Name = "Name4" });
myItemsSource.Add(new coSearchPoint { Name = "Name5" });
}
#endregion
#region INotifyPropertyChanged Member
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class coSearchPoint
{
public Guid Id { get; set; }
public String Name { get; set; }
public IRange FoundCell { get; set; }
public coSearchPoint()
{
Name = "";
Id = Guid.NewGuid();
FoundCell = null;
}
}
public interface IRange
{
string SomeValue { get; }
}
}
Here are 3 Classes:
MainWindow which set VM as his Datacontext
coImportReader the Class which presents your properties for your bindings
coSearchPoint which is just a Container for your information
IRange which is just an Interface
The Collection you are binding to needs to be a property of ir not a field.
Also try this :
public coImportReader ir { get; set; }
public <type of SearchPointCollection> irCollection { get { return ir != null ? ir.SearchPointCollection : null; } }
Bind to irCollection and see what errors you get if any.
The DisplayMemberPath should contain the property name of the elements in your collection. Assuming the elements in the SearchPointCollection are of the type SearchPoint and this class has a Property SearchPointName you should set DisplayMemberPath like this:
SearchRefPoint.DisplayMemberPath = "SearchPointName";
Edit:
In your code the class coSearchPoint has the Field Name defined in the Constructor. Name has to be a Property of the class, otherwise the Binding can't work.