I have a FlowListView control that is not displaying the items from a list. For test purposes I just wanted to display file path of the photo as a label.
Here 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:controls="clr-namespace:XLabs.Forms.Controls;assembly=XLabs.Forms.Controls"
xmlns:flv="clr-namespace:DLToolkit.Forms.Controls;assembly=DLToolkit.Forms.Controls.FlowListView"
x:Class="TamarianApp.ImagePage">
<ContentPage.Content>
<flv:FlowListView FlowColumnCount="3" SeparatorVisibility="None" HasUnevenRows="true" x:Name="image_gallary" ItemsSource="{Binding photos}" HeightRequest="100" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<Label Text="{Binding filepath}" TextColor="Black" Margin="20" VerticalOptions="Fill" HorizontalOptions="Fill" XAlign="Center" YAlign="Center"></Label>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
</ContentPage.Content>
</ContentPage>
Here is the backend:
public partial class ImagePage : ContentPage
{
public List<Photo> photos = App.rug.photos
public ImagePage()
{
InitializeComponent();
Title = "Photos";
}
}
'App.rug.photos' is a List of the Photo class which contains the string field 'filepath'. Debugging shows field 'photos' is not empty and contains the data from 'App.rug.photos.'
Please Help.
It may be to do with how you are assigning the list, you need to make sure that the list is a Property in order for the binding to work correctly.
Try..
public partial class ImagePage : ContentPage
{
public List<Photo> photos {get; set;}
public ImagePage()
{
InitializeComponent();
Title = "Photos";
photos = App.rug.photos
}
}
You may also need to set the binding context
public partial class ImagePage : ContentPage
{
public List<Photo> photos {get; set;};
public ImagePage()
{
InitializeComponent();
this.BindingContext = this;
Title = "Photos";
photos = App.rug.photos
}
}
Try this code.
public ObservableCollection<object>() photos;
...
var oc = new ObservableCollection<Photo>();
foreach (var item in App.rug.photos)
oc.Add(item);
photos = oc as ObservableCollection<object>;
I was having trouble with ObservableCollection<MyObject>, but solved it by using ObservableCollection<object>.
Related
I have a xaml page that contains two instances of the same content view. The content view have a datepicker which should update a value in the parent view model ( each content view should update a different variable in the view model). I tried to do the bindiable property but it's not working. I set the BindingMode to TwoWay but that's not working.
The issue is that the binding is not working from the contentview to the parent viewmodel through the bindiable property. Any input is much appreciated.
Below is my code:
MainPage.xaml
<?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"
BackgroundColor="{DynamicResource PageBackgroundColor}"
xmlns:picker="clr-namespace:TestSync.View"
xmlns:viewmodel="clr-namespace:TestSync.ViewModel"
x:DataType="viewmodel:TimeTrackerViewModel"
x:Class="TestSync.MainPage">
<VerticalStackLayout>
<Label Text="{Binding SelectedDate}"/>
<Label Text="{Binding SelectedDate1}"/>
<picker:DateTimePickerContentView CardTitle="First DatePicker" CardDate="{Binding SelectedDate,Mode=TwoWay}" />
<picker:DateTimePickerContentView CardTitle="Second DatePicker" CardDate="{Binding SelectedDate1,Mode=TwoWay}" />
</VerticalStackLayout>
</ContentPage>
TimeTrackerViewModel.cs
namespace TestSync.ViewModel
{
public partial class TimeTrackerViewModel :ObservableObject
{
[ObservableProperty]
public DateTime selectedDate;
[ObservableProperty]
public DateTime selectedDate1;
}
}
DateTimePickerContentView.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodel="clr-namespace:TestSync.View"
x:DataType="viewmodel:DateTimePickerContentView"
x:Class="TestSync.View.DateTimePickerContentView"
>
<VerticalStackLayout>
<Label Text="{Binding CardTitle}"/>
<DatePicker x:Name="myDate" Date="{Binding CardDate}" />
</VerticalStackLayout>
</ContentView>
and DateTimePickerContetntView.xaml.cs
namespace TestSync.View;
public partial class DateTimePickerContentView : ContentView
{
public static readonly BindableProperty CardTitleProperty = BindableProperty.Create(nameof(CardTitle), typeof(string), typeof(DateTimePickerContentView), string.Empty);
public string CardTitle
{
get => (string)GetValue(DateTimePickerContentView.CardTitleProperty);
set => SetValue(DateTimePickerContentView.CardTitleProperty, value);
}
public static readonly BindableProperty CardDateProperty = BindableProperty.Create(nameof(CardDate), typeof(DateTime), typeof(DateTimePickerContentView), defaultValue:DateTime.Parse("12/15/1992"),defaultBindingMode:BindingMode.TwoWay,propertyChanged:test);
private static void test(BindableObject bindable, object oldValue, object newValue)
{
var mytest= bindable as DateTimePickerContentView;
mytest.myDate.Date = (DateTime)newValue;
}
public DateTime CardDate
{
get => (DateTime)GetValue(DateTimePickerContentView.CardDateProperty);
set => SetValue(DateTimePickerContentView.CardDateProperty, value);
}
public DateTimePickerContentView()
{
InitializeComponent();
BindingContext = this;
}
}
I give you a workaround here.
For DateTimePickerContentView.xaml, define the BindingContext
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
...
x:Name="this">
<VerticalStackLayout BindingContext="{x:Reference this}">
<Label Text="{Binding CardTitle}"/>
<DatePicker x:Name="myDate" Date="{Binding CardDate}" />
</VerticalStackLayout>
</ContentView>
So for DateTimePickerContentView.cs, just delete this line
...
public DateTimePickerContentView()
{
InitializeComponent();
//BindingContext = this;
}
For data binding in a ContentView, you could refer to this official doc: Define the UI.
And if you want to set a default value, you should set it in TimeTrackerViewModel, because TimeTrackerViewModel's constructor execute after custom control set the default value. Then it will be replaced such as 1/1/1900 .
public TimeTrackerViewModel()
{
SelectedDate = DateTime.Parse("12/15/1992");
SelectedDate1 = DateTime.Parse("12/15/1992");
}
Hope it works for you.
I am trying to bind the ImageSource property of the NewGames class to the Source property of an Image control in a CarouselView but i keep getting the same error. Here is my code.
New Game Class
namespace FYP.ViewModels
{
public class NewGames
{
public int Id { get; set; }
public string GameTitle { get; set; }
public double Rating { get; set; }
public string ImageSource { set; get; }
}
}
This is my view model
using System.ComponentModel;
using System.Text;
namespace FYP.ViewModels
{
public class NewReleasesViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<NewGames> NewGames;
public ObservableCollection<NewGames> Games
{
get { return NewGames; }
set { NewGames = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Games"));
}
}
public NewReleasesViewModel()
{
Games = new ObservableCollection<NewGames>();
AddData();
}
private void AddData()
{
Games.Add(new NewGames
{
Id = 0,
GameTitle = "The Elder Scrolls Online",
Rating = 4.9,
ImageSource= "https://assets-prd.ignimgs.com/2022/01/05/elderscrollsonline- 1641413357689.jpg"
});
Games.Add(new NewGames
{
Id = 1,
GameTitle = "World Of Warcraft",
Rating = 4.9,
ImageSource = "https://assets-prd.ignimgs.com/2021/12/10/wow-1639126324635.jpg"
});
Games.Add(new NewGames
{
Id = 2,
GameTitle = "Star Wars: The Old Republic",
Rating = 4.9,
ImageSource = "https://assets-prd.ignimgs.com/2022/01/27/swotor-sq1-1643302998212.jpg"
});
}
}
}
And this is where i am trying to bind it to
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame HeightRequest="300"
WidthRequest="180"
BackgroundColor="white"
Padding="0"
CornerRadius="10"
HasShadow="True"
Margin="15"
HorizontalOptions="CenterAndExpand">
<Grid>
<StackLayout BackgroundColor="DimGray">
<Image Source="{Binding ImageSource}" Aspect="AspectFill"></Image>
</StackLayout>
<StackLayout Margin="-5">
<Label Text="{Binding GameTitle}"
TextColor="PaleGoldenrod"
FontSize="18"
FontAttributes="Bold"
Margin="15"
VerticalOptions="EndAndExpand"/>
</StackLayout>
</Grid>
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
It seems i am binding the NewGames class correcly because the carouselView is getting populated but for some reason none of the properties of the class go through and i don't know why. Hopefully this helps you guys understand was i am trying to do.
You're using compiled bindings when you're using the x:DataType property.
In order to make your bindings work like you expect, you need to explicitly set the x:DataType whenever the context changes. This is usually the case when you're using a DataTemplate.
In your case this would be something like this:
<CarouselView ItemsSource={Binding Games}>
<CarouselView.ItemTemplate>
<DataTemplate
x:DataType="local:NewGames">
<StackLayout>
<!-- ... -->
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
Where it says "local:NewGames" you need to put the correct namespace and class name.
I have a Xamarin CarouselView initialized with an empty ObservableCollection. The Carousel view correctly shows the EmptyView on page load.
However, when I add an item to the Observable Collection iOS throws the following exception:
attempt to insert item 0 into section 0, but there are only 0 items in section 0 after the update
The way I'm populating the item source is after the page loads I've hooked a button to an event handler to add items to the Observable Collection.
If I initialize the Observable Collection with initial data then the CarouselView works fine, it's just when I add items later that it breaks.
Page:
...
<ContentPage.BindingContext>
<viewModel:MatchesPageViewModel></viewModel:MatchesPageViewModel>
</ContentPage.BindingContext>
<CarouselView Margin="-10,15,-10,0"
ItemsSource="{Binding PendingMatches}"
HorizontalOptions="FillAndExpand"
HorizontalScrollBarVisibility="Never"
IsSwipeEnabled="True"
VerticalOptions="StartAndExpand">
<CarouselView.EmptyView>
<Frame>
<Label Text="It's empty"></Label>
</Frame>
</CarouselView.EmptyView>
<CarouselView.ItemTemplate>
<DataTemplate>
<Label Text="Some Content"></Label>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
View Model:
public class MatchesPageViewModel : BaseViewModel
{
public ObservableCollection<MatchResponse> PendingMatches { get; set; } =
new ObservableCollection<MatchResponse>();
//pretend it's invoked from a button in the page
public void SomeEventHandler()
{
//throws exception: attempt to insert item 0 into section 0...
PendingMatches.Add(new MatchResponse());
}
}
I guess that you may have some problem about adding item in observablecollection for Button click method. I do one demo using Button command binding that you can take a look.
<CarouselView
HorizontalOptions="FillAndExpand"
HorizontalScrollBarVisibility="Never"
IsSwipeEnabled="True"
ItemsSource="{Binding PendingMatches}"
VerticalOptions="StartAndExpand">
<CarouselView.EmptyView>
<Frame>
<Label Text="It's empty" />
</Frame>
</CarouselView.EmptyView>
<CarouselView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding str}" />
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<Button
x:Name="btn1"
Command="{Binding command1}"
Text="add data" />
public partial class Page2 : ContentPage
{
public Page2()
{
InitializeComponent();
this.BindingContext = new MatchesPageViewModel();
}
}
public class MatchesPageViewModel
{
public ObservableCollection<MatchResponse> PendingMatches { get; set; } = new ObservableCollection<MatchResponse>();
public ICommand command1 { get; set; }
public MatchesPageViewModel()
{
command1 = new Command(SomeEventHandler);
}
public void SomeEventHandler()
{
//throws exception: attempt to insert item 0 into section 0...
PendingMatches.Add(new MatchResponse() { str = "test" });
}
}
public class MatchResponse
{
public string str { get; set; }
}
I have a List of strings in my main page,
which I want to pass to another view. The attribute,
to whom I writing to has a binding. Essentially it's
the ItemSource of the ListView.
I tried additionally to List, both string[] and ObservableCollection as data storage
for Projects, since the documentation recommends to use it.
Trying to use the -element from MyView.xaml in MainPage.xaml works like charm.
MainPage.xaml:
Content = new MyView { Projects = MyProjects };
MyView.xaml.cs:
public partial class MyView : ContentView
{
private List<string> _projects;
public List<string> Projects
{
get => _projects;
set
{
_projects = value;
OnPropertyChanged(nameof(Projects));
}
}
//public string[] Test = new string[] { "Test", "Hallo", "Welt" };
public MyView ()
{
InitializeComponent();
}
}
MyView.xaml:
<StackLayout>
<ListView BackgroundColor="Red" x:Name="p_lstView" RowHeight="160" SeparatorColor="DodgerBlue" ItemsSource="{Binding Projects}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding}"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
You have some issues with the BindingContext on the ListView.ItemSource. The current context is on the MainPage.
You need to change:
<ListView BackgroundColor="Red" x:Name="p_lstView" RowHeight="160" SeparatorColor="DodgerBlue" ItemsSource="{Binding Content.Projects}">
Alternative, you can set the BindingContext in the MyView constrctor.
You should set the BindingContext then the binding will work:
public MyView()
{
InitializeComponent();
BindingContext = this;
}
I'm struggling with databinding in Xamarin Forms. Here's why, what I expect to happen from the following XAML statement:
IsVisible="{Binding Path=UserContext.IsLoggedOut}"
Is - That the property is bound to a child object of the View Model. Now either this isn't supported in Xamarin Forms or I am missing a trick.
If this is not supported in Xamarin, where it is supported in WPF, then what are we supposed to do in order to propagate nested objects? Flattening the View Model is making me write reams and reams of inelegant code.
Nested properties are supported, like other pretty complex expression as well:
You can test it:
Xaml
<StackLayout Spacing="20">
<StackLayout Orientation="Horizontal">
<Label Text="Subproperties: " HorizontalOptions="FillAndExpand" FontSize="15"></Label>
<Label Text="{Binding Item.SubItem.Text}" HorizontalOptions="FillAndExpand" FontSize="15"></Label>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Indexer: " HorizontalOptions="FillAndExpand" FontSize="15"></Label>
<Label Text="{Binding Item.Dictionary[key].Text}" HorizontalOptions="FillAndExpand" FontSize="15"></Label>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Array Indexer: " HorizontalOptions="FillAndExpand" FontSize="15"></Label>
<Label Text="{Binding Item.Array[1].Text}" HorizontalOptions="FillAndExpand" FontSize="15"></Label>
</StackLayout>
</StackLayout>
Page
public partial class Page2 : ContentPage
{
public ItemModel Item { get; }
public Page2()
{
InitializeComponent();
Item = new ItemModel();
BindingContext = this;
}
}
public class ItemModel
{
public ItemSubModel SubItem { get; set; }
public Dictionary<string, ItemSubModel> Dictionary { get; set; }
public ItemSubModel[] Array { get; set; }
public ItemModel()
{
SubItem = new ItemSubModel();
Dictionary = new Dictionary<string, ItemSubModel>
{
{"key", new ItemSubModel()}
};
Array = new [] {new ItemSubModel(), new ItemSubModel() };
}
}
public class ItemSubModel
{
public string Text { get; set; } = "Supported";
}
Result
I'm assuming you're trying in Xaml. Try removing 'Path'.
IsVisible="{Binding UserContext.IsLoggedOut}"
However more importantly, what is your BindingContext? For the above code to work you would need the BindingContext to be set to class Foo, which has a property called UserContext, which itself has a property IsLoggedOut.
Have a look here as well