super Curstom value converters in Xamarin - xamarin

I have a Xamarin forms app where I exhibit the result from different sensors all in one ListView. Every sensor populates a 'DataTemplate' ViewCell where I show the name, location and others and, in the central grid the values.
The point is that the visualization for those values should be different from each sensor (imagine I want a moving arrow for the wind, a growing blue box for the collected water, a number for the temperature, and so on)
Is it possible to return a custom UIElement, or a Grid or something from a IValueConverter and be able to do this task ?
If not, what would you recommend ?
PS: I think that, what I want to do, is done via ContentPresenter. but .. I cannot find proper details on how to achieve it.

Sounds like DataTemplateSelector should solve your issue:
A DataTemplateSelector can be used to choose a DataTemplate at runtime
based on the value of a data-bound property. This enables multiple
DataTemplates to be applied to the same type of object, to customize
the appearance of particular objects. This article demonstrates how to
create and consume a DataTemplateSelector.
Official docs: Creating a Xamarin.Forms DataTemplateSelector
Creating a DataTemplateSelector:
public class PersonDataTemplateSelector : DataTemplateSelector
{
public DataTemplate ValidTemplate { get; set; }
public DataTemplate InvalidTemplate { get; set; }
protected override DataTemplate OnSelectTemplate (object item, BindableObject container)
{
return ((Person)item).DateOfBirth.Year >= 1980 ? ValidTemplate : InvalidTemplate;
}
}
Consuming a DataTemplateSelector in XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Selector;assembly=Selector" x:Class="Selector.HomePage">
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="validPersonTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="invalidPersonTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<local:PersonDataTemplateSelector x:Key="personDataTemplateSelector"
ValidTemplate="{StaticResource validPersonTemplate}"
InvalidTemplate="{StaticResource invalidPersonTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>
<ListView x:Name="listView" ItemTemplate="{StaticResource personDataTemplateSelector}" />

Related

Xamarin Forms ListView not Updating from ObservableCollection

The ListView of my XAML file is being filled with a ViewModel that has an ObservableCollection from a service but the ListView is not showing the information. I already check that the service is returning the correct information.
This is the code of my XML file :
<?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="XAMLUnit1.Views.NetflixRoulettePage" Title="Movie">
<ContentPage.Content>
<StackLayout>
<AbsoluteLayout>
<BoxView Color="Gray" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1"></BoxView>
<SearchBar x:Name="searchBar"
Placeholder="Search By Actor's name"
PlaceholderColor="White"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0.1,0.1,1,1" SearchCommand="{Binding SearchMovieCommand}" ></SearchBar>
</AbsoluteLayout>
<ListView x:Name="ListOfMovies"
ItemsSource="{ Binding MovieList}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<ImageCell
ImageSource="{Binding poster_path, StringFormat='https://image.tmdb.org/t/p/w500{0}'}">
</ImageCell>
<StackLayout Orientation="Horizontal">
<TextCell Detail="{Binding title}"></TextCell>
<TextCell Detail="{Binding release_date}"></TextCell>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
This is the ViewModel that calls the service and it's uses its ObservableCollection as ItemsSource for the ListView :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using XAMLUnit1.Models;
using XAMLUnit1.ServiceImpl;
using XAMLUnit1.Services;
namespace XAMLUnit1.ViewModels
{
public class MovieRouletteViewModel
{
IMovieService service;
public ObservableCollection<Movie> MovieList { get; set; }
public ICommand SearchMovieCommand { get; set; }
public MovieRouletteViewModel()
{
service = new MovieServiceFinder();
SearchMovieCommand = new Command(GetMovies);
}
private void GetMovies()
{ var list= service.GetMovies("");
MovieList = list;
}
}
}
public partial class NetflixRoulettePage : ContentPage
{
MovieRouletteViewModel viewModel;
public NetflixRoulettePage()
{
InitializeComponent();
viewModel = new MovieRouletteViewModel();
BindingContext = viewModel;
}
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
}
}
Do not set the ObservableCollection to a new List it will break the binding. Clear the items from the list and add the new items to it.
public MovieRouletteViewModel()
{
service = new MovieServiceFinder();
SearchMovieCommand = new Command(GetMovies);
MovieList = new ObservableCollection<Movie>();
}
private void GetMovies()
{
var list= service.GetMovies("");
MovieList.Clear();
foreach(Movie movie in list)
{
MovieList.Add(movie);
}
}
There is a few things i want to note on this problem, though mainly as the comments rightly illuminate, replacing the collection with a new collection breaks the bindings. hence why its not updating.
However there are several solutions to consider.
Implement INotifyPropertyChanged on the collection and replace the whole collection like you are doing, This will work fine. However it does resets the scroll and basically recalculates everything from scratch. Some would say this defeats the point of an ObservableCollection as they have their own built in notification plumbing, Yeah it does. Though, if you are replacing the whole Collection with a new collection then you are going to save on oodles of Cyclic calculations when compared to cleaRing and adding each item back individually Which will basically fire for every update
Call Clear and Add on each item individually.. If the collection hasn't changed much, you can even take it a step further by just comparing the 2 collections and updating whats needed. However once again, if the collections are dissimilar, then this approach is still expensive, and on a mobile device you want to minimize screen recalculations where ever possible .
Create a subclassed collection, implement your own replace/update, and Add/Remove range methods, and INotifyPropertyChanged giving you the best of both worlds, this will allow atomic modification of the collection and then you can fire property changed event once.
There are many examples of all these approaches online, its just worth noting clearing and adding items sometimes is not the best approach for mobile devices. It depends how big your collections are, how much is changing and why.
If it is a completely different list, then replacing the collection is fine in my opinion.
If its the same list, then well you may want to compare and modify to save on property changes
Anyway good luck
I disagree with other answers. You can set the Movies as many times as you want, this is not the problem.
The problem is just that your viewmodel doesn't implement INotifyPropertyChanged.
Your UI is just not notified when you set your ObservableCollection.
Your service command is actually replacing the ObservableCollection, you need to change your GetMovies() method to
var list = service.GetMovies("");
MovieList.clear();
foreach(Movie m in list)
{ MovieList.Add(m);}

How to preview an ItemTemplate in the XAML designer

Say I have a simple ItemTemplate for a ListView
<ListView ItemsSource="{x:Bind ListItems}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:TextItem">
<TextBlock Text="{x:Bind Item}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
MainPage.xaml
When I run the app, I get a ListView populated with TextBlocks - one for each item in ListItems (which is a behind defined variable). However, in the XAML designer, I see nothing.
So, is there a way to preview the ItemTemplate/DataTemplate in the XAML designer, with a set number of placeholder TextBlocks with placeholder text replacing the Text="{x:Bind Item}"? Or just preview a single TextBlock?
This is not a duplicate of
Design View of a DataTemplate in a ResourceDictionary in Visual Studio - because I can't use Blend. Everything I've looked up that says use Blend in a certain way merely gives me the message 'This feature is not available for projects targeting "Windows 10 Fall Creators Update (10.0; Build 16299)"'
Can I preview my DataTemplate(s) in my collection-based controls in the designer? - well, perhaps it is, but I don't understand the accepted answer, which is now several years old. An answer containing an example tailored to my question would be really helpful.
Microsoft actually posted a (semi helpful) tutorial on this in April of this year: link
Requirements
Whatever you want to display, you will need this at the top of your XAML code (should be there by default if you created the page using VS's templates):
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
A simple list of strings
For something simple like a list of strings, something like this will work just fine (taken from the above mentioned MSDN page):
<ListView>
<d:ListView.Items>
<system:String>Item One</system:String>
<system:String>Item Two</system:String>
<system:String>Item Three</system:String>
</d:ListView.Items>
</ListView>
Display something more complex
If you want to use a data source from somewhere else in your code, it gets a little more ugly:
First you need to set your data context (usually done at the page level, but you can also add it just on the element where you want to use it):
d:DataContext="{d:DesignInstance
yournamespace:YourDataSourceClass,
IsDesignTimeCreatable=True}"
And on the ListView add:
d:ItemsSource="{Binding data}"
If you are using an ItemTemplate, you will need to add "d:" variants for everything you want to pull from your data source.
Full example code:
XAML:
<Page
x:Class="exampleApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:exampleApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:DesignTimeData,
IsDesignTimeCreatable=True}"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid>
<ListView ItemsSource="{x:Bind ListItems}" d:ItemsSource="{Binding DummyData}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:TextItem">
<TextBlock Text="{x:Bind Item}" d:Text="{Binding Item}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
</Page>
C#:
using System.Collections.ObjectModel;
using Windows.UI.Xaml.Controls;
namespace exampleApp {
public sealed partial class MainPage : Page {
public ObservableCollection<TextItem> ListItems;
public MainPage() {
this.InitializeComponent();
//populate you ListItems list
}
}
public class TextItem {
public string Item { get; set; }
}
public class DesignTimeData {
public ObservableCollection<TextItem> DummyData { get; set; } = new ObservableCollection<TextItem> {
new TextItem { Item = "foo" },
new TextItem { Item = "bar" },
new TextItem { Item = "bla" },
new TextItem { Item = "blubb" }
};
}
}
Remarks
It seems you have to use "Binding" rather than "x:Bind" for the design-time bindings. From what I understand, x:Bind works using code generated at compile time, which is not executed by the XAML designer
When you change your Binding declarations or data source code, you will have to recompile to see the changes in the designer. Other design changes will reflect in real time
Binding is more limited than x:Bind in some regards (notably you cannot bind to class methods using Binding, only x:Bind). Keep that in mind when writing your design-time data
Sadly some of the more convenient ways of getting design-time data into your app are WPF and Xamarin only (Blends' data window, data declared in XAML file, and especially auto-generated sample data)
(Everything was tested using Visual Studio 2019 version 16.10.3.)

how to detect when data binding to UI elements (panorama and listbox) have completed in windows phone 7?

i have a Panorama control and ListBox controls inside the Panorama. is there any "event" that i can hook on to or any way to detect when all the data binding or UI display associated with the Panorama and/or ListBox controls are finished?
the reason i need to detect this event is because i want to show the ApplicationBar only after the Panorama and/or ListBox controls have completely binded and finished rendering.
for example, my XAML is defined as the following.
<controls:Panorama Name="panorama">
<controls:Panorama.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Details}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Field1}"/>
<TextBlock Text="{Binding Field2}"/>
...
<TextBlock Text="{Binding FieldN}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</controls:Panorama.ItemTemplate>
</controls:Panorama>
my plain-old CLR object (POCO) looks like the following.
public class MyPoco {
List<Detail> Details { get; set; }
}
public class Detail {
public string Field1 { get; set; }
public string Field1 { get; set; }
...
public string FieldN { get; set; }
}
in my c# code-behind, i bind the data as follows.
List<MyPoco> pocos = GetMyPocosFromSomewhere();
panorama.ItemsSource = myList;
ApplicationBar.IsVisible = true; //i only want to make this visible after the Panorama and ListBox controls have finished binding and rendering
right now, the code as i have sketched out above works, but the ApplicationBar is always visible before the Panorama/ListBox controls have rendered. to me, this makes the user experience awkward.
any help is appreciated.
Short answer would be "no, you can't detect it".
But a good solution is to add the command to the UI work queue aka. the Dispatcher. Like this:
Dispatcher.BeginInvoke(() => ApplicationBar.IsVisible = true);
That way, it'll first render it, when all the other UI tasks are done, and the experience shouldn't be so awkward.

WP7 binding UserControl using MVVM [duplicate]

This question already has an answer here:
Closed 11 years ago.
Possible Duplicate:
Binding between Usercontrol with listbox and parent control (MVVM)
I’m trying to bind a UserControl to property on my main page’s viewmodel
The code looks like this:
UserControl xaml:
<UserControl x:Class="myUserControl" ....>
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}" >
<ListBox Name="myListBox" ItemsSource="{Binding Path=myItemsSource}"/>
</Grid>
</UserControl>
the codebehind looks like this:
public partial class myUserControl : UserControl
{
public static DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("myItemsSource",
typeof(IEnumerable), typeof(myUserControl), null);
public IEnumerable myItemsSource
{
get
{
return (IEnumerable)GetValue(ItemsSourceProperty);
}
set
{
SetValue(ItemsSourceProperty, value);
}
}
}
The UC is used like this in the main page:
<phone:PhoneApplicationPage DataContext="{Binding myViewModel, Source={StaticResource Locator}}" ....>
<Grid x:Name="ContentPanel">
<uc:myUserControl x:Name="ucList" myItemsSource="{Binding Path=DataList}"/>
</Grid>
</phone:PhoneApplicationPage>
and the viewModel for the main page look like this:
public class myViewModel : ViewModelBase
{
public ObservableCollection<myObject> DataList
{
get
{
return _datalist;
}
set
{
if (_dataList != value)
{
_dataList = value;
RaisePropertyChanged("DataList");
}
}
}
}
But when the DataList property is set, the uc List in not populated.
What i'm I missing ?
It's still early and I haven't had my coffee yet, but it looks to me like you might need a data template for your list items. You've defined the ItemsSource but not told the control how to render the items themselves.
What does your list contain? Try binding a data template to one of the list item properites, like this:
<UserControl x:Class="myUsercontrol" ....>
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}" >
<ListBox Name="myListBox" ItemsSource="{Binding Path=myItemsSource}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SomeListItemProperty}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
EDIT: Are you by chance using MVVM Light? I saw that you mentioned {StaticResource = Locator} in one of the comments. That would be helpful to know.
One other thing you might try is checking if your UserControl is in its own namespace. If you put it in a UserControl folder, it might be. I've had some issues with binding a UserControl when it wasn't in the same namespace as the ViewModel.

WP7: Binding to an element outside pivot.itemtemplate

I've been struggling for a while on this. I'm a bit of a newbie, but i lurked a lot and still couldn't find a solution to my problem, so I decided to post my question here.
I'm binding a pivot to a collection of objects I want to display to create a gallery. Here is my pivot control bound to a list called gallery, where each object contains 2 strings (url and description).
<controls:Pivot ItemsSource="{Binding gallery}" Grid.Row="0" x:Name="galleryPivot">
<controls:Pivot.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Image Source="{Binding url}" />
<Grid Visibility="{Binding ElementName=galleryPivot, Path=DataContext.ShowDetail}">
<ListBox>
<ListBoxItem>
<StackPanel>
<TextBlock Text="{Binding description}" />
</StackPanel>
</ListBoxItem>
</ListBox>
</Grid>
</Grid>
</StackPanel>
</DataTemplate>
</controls:Pivot.ItemTemplate>
</controls:Pivot>
The datacontext is the viewmodel and initialized in the constructor of the page.
Here is my ViewModel:
public class GalleryViewModel : INotifyPropertyChanged
{
public List<Gallery> gallery
{
get { return Globals.pictures; }
}
private Visibility _showDetail = Visibility.Collapsed;
public Visibility ShowDetail
{
get { return _showDetail; }
set {
_showDetail = value;
RaisePropertyChanged("ShowDetail");
}
}
public GalleryViewModel()
{ }
public event PropertyChangedEventHandler PropertyChanged = delegate { return; };
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The gallery object is a list in my ViewModel, as the ShowDetail property. As ShowDetail is outside the scope, I tried to set ElementName as explained here.
The pivot binds well to the gallery list, but when I change the value of ShowDetail, the grid won't hide. I also tried to set ElementName to LayoutRoot but it still won't work.
My question, how can I bind the visibility when it is outside the scope of the itemtemplate?
Within a DataTemplate the ElementName binding refers only to the names of elements that are within that DataTemplate. The data context within your data template is the Gallery instance, not the GalleryViewModel. You could move the ShowDetail property down to the Gallery class instead.
If you'd rather not do that, then an alternative would be to use a proxy for the data context, which you add as a resource to the page and bind to the page's data context (a GalleryViewModel instance presumably). You can then reference that resource as you would any other resource to get at the parent data context.
If you're not familiar with this proxy concept, then Dan Wahlin's post on the subject should help.

Resources