I have a ListView with a data template. I am trying to have a custom component that supports binding for the content of the data template.
Here is the ListView in the page:
<ListView ItemsSource="{Binding List}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="entities:ListItem">
<ViewCell>
<components:ListItemView ListItem="{Binding}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And this is the ListItemView declaration:
public partial class ListItemView : StackLayout
{
public static readonly BindableProperty ListItemProperty
= BindableProperty.Create(
nameof(ListItem), typeof(ListItem), typeof(ListItemView), null,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: ListItemPropertyChanged);
static void ListItemPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = (ListItemView)bindable;
view.ListItem = (ListItem)newValue;
}
public ListItem ListItem
{
get => (ListItem)GetValue(ListItemProperty);
set
{
SetValue(ListItemProperty, value);
if (_viewModel != null) // never hits this break point
_viewModel.ListItem = value;
}
}
I had a breakpoint on the line with the comment. This breakpoint was never hit. ListItemView however does get initialized and created.
Update
I tried a simple demo to ensure the issue was in the binding,
<StackLayout Padding="5">
<Label Text="{Binding Demo.Title}" />
<components:CheckListView ListItem="{Binding Demo}" />
</StackLayout>
The above code was outside the list view and I am able to see the title. The breakpoint is still not hit.
Related
I have the following situation:
I have an app, where I want to create notes using a special page for it. I have VM with ObservableCollection NoteItems with all the notes. When I want to create a new note, I add a new note to this ObservableCollection and pass this new note using BindingContext
var editingPage = new EditingPage();
editingPage.BindingContext = NoteItems[NoteItems.Count - 1].Text;
Application.Current.MainPage.Navigation.PushAsync(editingPage);
In the editing page I have an Entry field
<Entry
x:Name="EdtingPageEntryField"
Text="{Binding Text}"
Placeholder="Enter a note"
/>
, which is Binding his text with the Text parameter of a NoteItem.
The problem is that if I change the text in entry field, it does not automatically apply to a Text parameter of a NoteItem. That is why I want to pass this text when I close this EditingPage(go back to the MainPage). So the question is, how can I pass this Text parameter to a NoteItem element from NoteItems ObservableCollection, which is located in VM.
UPD. The value of NoteItem, which is located in NoteItems does not change
UPD2. I was wrong about the value of NoteItem, it has been changed, but the new value does not display on MainPage, that is why I used INotifyPropertyChanged, but it did not work.
Here is Note Item class
public class NoteItem
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string text;
public string Text
{
get { return text; }
set
{
if(text != value)
{
text = value;
NotifyPropertyChanged();
}
}
}
And MainPage.xaml:
<ContentPage.BindingContext>
<local:NoteItemViewModel/>
</ContentPage.BindingContext>
<FlexLayout>
<ListView ItemsSource="{Binding NoteItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Text}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</FlexLayout>
'''
in order to dynamically update the UI, your model must implement INotifyPropertyChanged
public class NoteItem : INotifyPropertyChanged
{
...
}
simply adding a PropertyChanged method is not the same as implementing INotifyPropertyChanged. You must add the interface to the class definition so that the binding mechanism knows that your class has implemented it
I've created a customer specific task management app with tasks placed on specific dates (and sometime hours), but here the date is important.
I'm using a listView and have a DatePicker setting for selected other dates than today. So far so good.
I would like to implement a week quick-filter option so that e.g., the dates of the current week is displayed at the top of the list view and a click on a certain date would filter the listView accordingly. Kind of a standard outlook-like week view.
How would I do this in the best way?
CustomControl that I put above the listView?
ViewPager control?
Any ideas or suggestions much appreciated.
P.S. I need to be able to target both Android and iOS.
Set two Properties in the ViewModel one for containing all the Items EntireCollection and another to store the Filtered Items FilteredCollection. On button click derive the Filtered item from entire list using Where.
ViewModel
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ListItem> filteredCollection;
public ObservableCollection<ListItem> FilteredCollection
{
get
{
return filteredCollection;
}
set
{
filteredCollection = value;
OnPropertyChanged();
}
}
private ObservableCollection<ListItem> entireCollection;
public ObservableCollection<ListItem> EntireCollection
{
get
{
return entireCollection;
}
set
{
entireCollection = value;
OnPropertyChanged();
}
}
public ViewModel()
{ ...
this.FilterCollection = this.EntireCollection;
...
}
}
Button clicked
void Button_Clicked(System.Object sender, System.EventArgs e)
{
DateTime selectedDate = ((DateTime)((sender as VisualElement).BindingContext)).Date;
viewModel.FilteredCollection = new ObservableCollection<ListItem>(viewModel.EntireCollection.Where(x =>
{
if (DateTime.Equals(x.DateAdded, selectedDate))
{
var asd = x.DateAdded.Day;
return true;
}
return false;
}));
}
XAML
<StackLayout>
<ScrollView
x:Name="calender"
Orientation="Horizontal">
<StackLayout
BackgroundColor="Blue"
BindableLayout.ItemsSource="{Binding Dates}"
Orientation="Horizontal">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Button
TextColor="White"
BackgroundColor="Blue"
Clicked="Button_Clicked"
Text="{Binding Day}"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
<ListView
ItemsSource="{Binding FilteredCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}"/>
<Label Text="{Binding DateAdded}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Hope it helps!!
I'm using Xamarin.Forms MVVM to develop my app, and don't found what I'm doing wrong, I have an ObservableCollection with the values from web API, and when I set a break point all the values are good even in the view when I see the values of the binding source everything have the value, but the values are not showing up in my ListView.
Here is the ViewModel
class DatosMedicosViewModel : BaseViewModel
{
private ApiService apiService;
private ObservableCollection<Land> land;
private bool isRefreshing;
public ObservableCollection<Land> Lands
{
get { return this.land; }
set { SetValue(ref this.land, value); }
}
public bool IsRefreshing
{
get { return this.isRefreshing; }
set { SetValue(ref this.isRefreshing, value); }
}
public DatosMedicosViewModel()
{
this.apiService = new ApiService();
this.LoadLand();
}
private async void LoadLand()
{
this.IsRefreshing = true;
var connection = await this.apiService.CheckConnection();
if (!connection.IsSuccess)
{
this.IsRefreshing = false;
await Application.Current.MainPage.DisplayAlert(
"Error",
connection.Message,
"Accept");
await Application.Current.MainPage.Navigation.PopAsync();
return;
}
var response = await this.apiService.GetList<Land>(
"url Base",
"prefix",
"Controller");
if (!response.IsSuccess)
{
this.IsRefreshing = false;
await Application.Current.MainPage.DisplayAlert(
"Error",
response.Message,
"Accept"
);
return;
}
var list = (List<Land>)response.Result;
this.Lands = new ObservableCollection<Land>(list);
this.IsRefreshing = false;
}
public ICommand RefreshCommand
{
get
{
return new RelayCommand(LoadLand);
}
}
}
Here is the View
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ARLAPP.Views.ConsultaPage"
BackgroundColor="White"
BindingContext="{Binding Main, Source={StaticResource Locator}}"
Title="Lands">
<ContentPage.Content>
<StackLayout
BindingContext="{Binding Lands}"
Padding="5">
<StackLayout>
<Image
VerticalOptions="Center"
WidthRequest="300"
Source="UserIcon"
BackgroundColor="Transparent"/>
<Label Text="Mark"
VerticalOptions="Center"
HorizontalOptions="CenterAndExpand"
FontAttributes="Bold"
FontSize="Medium"/>
</StackLayout>
<StackLayout>
<ListView
SeparatorVisibility="Default"
FlowDirection="LeftToRight"
BackgroundColor="White"
ItemsSource="{Binding Lands}"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label
Grid.Column="2"
VerticalOptions="Center"
TextColor="Black"
Text="{Binding Currency}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Here how I call the view
if (this.PageName == "Lands")
{
MainViewModel.GetInstance().Lands= new LandViewModel();
Application.Current.MainPage = new LandMasterPage();
}
Check your BindingContext. I think you are setting it wrong in your view.
In your top-level StackLayout you set the the BindingContext to your property: BindingContext="{Binding Lands}". And in your ListView you set the ItemsSource also to this property: ItemsSource="{Binding Lands}". That won't work because the ListView is trying to bind to a property Lands inside your BindingContext, which is also set to Lands.
Remove the BindingContext from your top-level StackLayout, because you don't need it.
Ensure the BindingContext of your page ConsultaPage is set to your view-model DatosMedicosViewModel.
Sample of setting the bindingcontext (abstract code):
var mypage = new ConsultaPage();
mypage.BindingContext = new DatosMedicosViewModel();
await Navigation.PushAsync(mypage);
// Load your data in OnAppearing() of the page-event
This should solve your binding-problem.
Side-Note: As Abdul Gani said in the comments: Ensure you implement the INotifyPropertyChanged interface, but I assume you do this already in your BaseViewModel and call the NotifyChanged-Event in your SetValue-Method.
I have a custom control CustomTextBox.xaml:
<AbsoluteLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Core.Controls.CustomTextBox"
xmlns:controls="clr-namespace:MyApp.Core.Controls;assembly=MyApp.Core"
BackgroundColor="White">
<AbsoluteLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="OnTapped"/>
</AbsoluteLayout.GestureRecognizers>
<Entry x:Name="textValueEntry" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0.5, 1, 0.9, 0.9" FontAttributes="Bold"/>
<Label x:Name="placeholderLabel" AbsoluteLayout.LayoutFlags="PositionProportional" AbsoluteLayout.LayoutBounds="0.05, 0.5" FontSize="18"/>
</AbsoluteLayout>
I want to be able to bind to the textValueEntry control from the parent view. So I added a bindable property in CustomTextBox.xaml.cs:
private string _textValue;
public string TextValue
{
get
{
return _textValue;
}
set
{
_textValue = value;
}
}
public static BindableProperty TextValueProperty = BindableProperty.Create(nameof(TextValue), typeof(string), typeof(CustomTextBox), string.Empty, BindingMode.TwoWay, null,
(bindable, oldValue, newValue) =>
{
(bindable as CustomTextBox).textValueEntry.Text = (string)newValue;
});
I try to bind to it from the parent view like this:
<controls:CustomTextBox HorizontalOptions="FillAndExpand" VerticalOptions="CenterAndExpand" HeightRequest="50" TextValue="{Binding UsernamePropertyInViewModel, Mode=TwoWay}"/>
TextValue property gets set with UsernamePropertyInViewModel when I launch the app, as I can see it in textValueEntry. But when I change the text in textValueEntry it doesn't update UsernamePropertyInViewModel. How can I bind to it so it updates UsernamePropertyInViewModel when I change the text in textValueEntry?
As far as I can tell your CustomTextBox entry textValueEntry doesn't Bind to your TextValue property.
Also your TextValue property needs to look like this for BindableProperties. You need to set your BindableProperty to the appropriate value for a Binding. No need for a private backing variable to TextValue.
public string TextValue
{
get => (string)GetValue(TextValueProperty);
set => SetValue(TextValueProperty, value);
}
Parent.Xaml
<Entry x:Name="textValueEntry" Text="{Binding Path=TextValue, Source={x:Reference Page}}" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0.5, 1, 0.9, 0.9" FontAttributes="Bold"/>
Your Page will need an x:Name="Page" for the Binding Source
I have a custom UserControl that consists of a ListBox with a DataTemplate.
The ListBox gets it's source set in XAML and the elements in the DataTemplate gets it's values from Binding.
My UserControl XAML looks like this:
<UserControl x:Class="Test.UserControls.TracksListBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="clr-namespace:Test.Converters"
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
mc:Ignorable="d"
d:DesignHeight="480" d:DesignWidth="480"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Name="this">
<UserControl.Resources>
<converters:BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="transparent">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CheckBox Grid.Row="0" IsChecked="{Binding Show}"/>
<ListBox Grid.Row="1" Name="List">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Top"
Margin="0 5 0 5">
<TextBlock Text="{Binding Title}"/>
<CheckBox IsChecked="{Binding ElementName=this, Path=Show}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
And the code-behind:
namespace Test.UserControls
{
public partial class TracksListBox : UserControl
{
public static readonly DependencyProperty TracksListProperty =
DependencyProperty.Register("TracksList",
typeof(List<Track>),
typeof(TracksListBox),
new PropertyMetadata(null, OnTracksListChanged));
public static readonly DependencyProperty ShowProperty =
DependencyProperty.Register("Show",
typeof(bool),
typeof(TracksListBox),
new PropertyMetadata(false));
public TracksListBox()
{
InitializeComponent();
}
public List<Track> TracksList
{
get
{
return (List<Track>)GetValue(TracksListProperty);
}
set
{
SetValue(TracksListProperty, value);
}
}
public bool Show
{
get
{
return (bool)GetValue(ShowProperty);
}
set
{
SetValue(ShowProperty, value);
}
}
private static void OnTracksListChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
(obj as TracksListBox).OnTracksListChanged((List<Track>)args.OldValue, (List<Track>)args.NewValue);
}
protected virtual void OnTracksListChanged(List<Track> oldValue, List<Track> newValue)
{
List.ItemsSource = newValue;
}
}
}
In my MainPage.xaml I use it like this:
<userControls:TracksListBox x:Name="TopTracksListBox"
TracksList="{Binding ElementName=this, Path=TopTracks}"
Show="True"/>
My problem here is that the CheckBox inside the ListBox DataTemplate won't get the value from Show. The other CheckBox in Grid.Row="0" though, gets the correct value... How do I bind a value from my UserControl inside the DataTemplate of the ListBox?
This must be a bug, no mather what I tried the DataContext of the CheckBox always ended up being null or a Track. If you want to work around it you can add this until this gets fixed
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Top"
Margin="0 5 0 5">
<TextBlock Text="{Binding Title}"/>
<CheckBox Loaded="CheckBox_Loaded"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
and then set the Binding in code behind once it is Loaded
private void CheckBox_Loaded(object sender, RoutedEventArgs e)
{
CheckBox checkBox = sender as CheckBox;
Binding isCheckedBinding = new Binding("Show");
isCheckedBinding.Source = this;
checkBox.SetBinding(CheckBox.IsCheckedProperty, isCheckedBinding);
}
I dropped your code into a user control in an app, changed Track to string and didn't bind anything to the list but I still saw a checkbox displayed and the binding of Show to the IsChecked worked for me.