I am writing a Silverlight application which need to show some data.
There are two DataGrids:
First one shown "Users" class. This class contains several fields such as "Name", "Gender", "Grade", "Department", "Fingerprints". "Fingerprints" contains more than one "Fingerprint" Class.
Now I need to link two DataGrid together -- The first one is shown all users, and the second one show the fingerprints of one user which was selected in the first DataGrid.
BTW: I am using WCF Domain Service to supply data source for these two DataGrids.
UPDATE 1:
XAML code:
<sdk:Page.Resources>
<CollectionViewSource x:Key="studentFingerprintsViewSource" Source="{Binding Path=Data.Fingerprints, ElementName=dds_Main}" />
</sdk:Page.Resources>
<riaControls:DomainDataSource x:Name="dds_Main" QueryName="GetStudentsQuery">
<riaControls:DomainDataSource.FilterDescriptors>
<riaControls:FilterDescriptor Operator="Contains" PropertyPath="Number" Value="{Binding Text, ElementName=txt_UserID, Mode=TwoWay}"/>
</riaControls:DomainDataSource.FilterDescriptors>
<riaControls:DomainDataSource.DomainContext>
<ds:AllDomainContext />
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>
<sdk:DataGrid AutoGenerateColumns="True" ItemsSource="{Binding ElementName=dds_Main, Path=Data}" Margin="12,38,8,0" Name="dg_Main" RowDetailsVisibilityMode="VisibleWhenSelected" Height="168" VerticalAlignment="Top" />
<sdk:DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" ItemsSource="{Binding Source={StaticResource studentFingerprintsViewSource}}" Margin="406,320,0,0" Name="fingerprintsDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected" VerticalAlignment="Top" Width="400" />
class Fingerprint
{
int id;
/* something more */
}
Class Student
{
int id;
/* Fingerprints. I forgot the type. All models classes are generated by Entity Model Designer */
/* something more */
}
The first DataGrid can show all students correctly, the latter one always shown nothing...
Related
I'm trying to display some text -- or an image -- if there's no data for my ListView to display in my Xamarin Forms 5 app but I can't get it to work.
Here's what my ListView looks like:
<ListView
x:Name="CustomersList"
ItemsSource="{Binding Customers, TargetNullValue='No customers!'}"
BackgroundColor="Transparent">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell
Text="{Binding FullName}"
TextColor="{StaticResource PrimaryDark}"
Command="{Binding Source={x:Reference Name=CustomersList}, Path=BindingContext.CustomerSelectedCommand}"
CommandParameter="{Binding .}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The source for the ListView is set as follows in my ViewModel
ObservableRangeCollection<CustomerModel> customers;
public ObservableRangeCollection<CustomerModel> Customers
{
get => customers;
set
{
if (customers == value)
return;
customers = value;
OnPropertyChanged();
}
}
If my API call returns nothing, I do a Customers = null;
When I use TargetNullValue as shown above, I acutally get errors where I bind the TextCell to FullName. It shows:
'FullName' property not found on 'N', target property:
'Xamarin.Forms.TextCell.Text'
It then repeats this error message for every character in No Customers!.
I then tried using FallbackValue. This time I don't get an error but it simply doesn't show the "No Customers!" message.
How do I handle a ListView having no data to show? At the very least, I'd like to show some simple text message but it would be even better to show something nicer, maybe an image.
If you are ok to switch from ListView to CollectionView, you can use the built-in CollectionView.EmptyView:
<CollectionView ItemsSource="{Binding Customers}"
EmptyView="No customers!" ... />
With the same EmptyView you can define whatever view:
<CollectionView>
<CollectionView.EmptyView>
<ContentView>
<image ... />
</ContentView>
</CollectionView.EmptyView>
</CollectionView>
Docs: CollectionView EmptyView
Is it possible to display a list of ImageSources in XAML with Xamarin like you do with an List of Strings? In string you would do:
<ListView x:Name="newsList" ItemsSource="{Binding newsList}" ItemTapped="listView_ItemTapped" IsPullToRefreshEnabled="True" RefreshCommand="{Binding RefreshNewsCommand}" IsRefreshing="{Binding IsCurrentlyRefreshing}" />
However if you do this with an list of ImageSources it will not work.
Does anyone know an appropriate way to load this list of images into my few? Preferably so that i can load them next to the items of my NewsList?
Yes, you can define an "ItemTemplate" on the ListView (xaml / c#). There are 2 predefined ViewCells that you can assign to the ItemTemplate. One of them is "ImageCell". But ImageCell displays an Image + Text.
Also you might be interested in this:
https://developer.xamarin.com/guides/xamarin-forms/user-interface/listview/customizing-cell-appearance/
You will have to create a custom ViewCell.
Something like this (pseudo code):
ViewCell:
<ViewCell>
<ViewCell.View>
<Image Source="{Binding CoolImageSource}" />
</ViewCell.View>
</ViewCell>
//Your ListView, bring the namespace in, to reference your viewcell
//You can also define the item directly inside of the DataTemplate
//But keep your code clean and hold the viewcells separately
<ListView xmlns:vc=YourNameSpace.ViewCellsFolder>
<ListView.ItemTemplate>
<DataTemplate>
<vc:MyCoolViewCell />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
//And finally assign a List of such model:
public class ImageItem
{
public ImageSource CoolImageSource { get; set; }
}
I'm working on a view (called 'Familify') which shows users a list of assets, and allows them to delete an asset from the list. The assets are stored in an ObservableCollection in the ViewModel, so the command to delete simply takes the asset object and removes it from collection. I'm having issues getting the 'delete' functionality working. Here is the XAML and codebehind:
Familify.xaml
<ListView
ItemsSource="{Binding Assets}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80px" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="150px" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60px" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Text="{Binding number}" FontFamily="Consolas"/>
<TextBlock
Grid.Column="1"
Text="{Binding type}"/>
<TextBlock
Grid.Column="2"
Text="add binding here"/>
<TextBlock
Grid.Column="3"
Text="add binding here"/>
<Button
Command="{x:Bind ViewModel.RemoveAssetCommand}"
CommandParameter="{Binding}"
Content=""
FontFamily="Segoe MDL2 Assets"
Grid.Column="4">
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Familify.xaml.cs
namespace asset_manager.Views
{
public sealed partial class Familify : UserControl
{
FamilifyViewModel ViewModel { get; set; }
public Familify()
{
this.InitializeComponent();
DataContextChanged += (s, e) =>
{
ViewModel = DataContext as FamilifyViewModel;
};
}
}
}
The idea is that clicking the button removes the asset from the list. (Just to note, the normal binding showing number, type, etc. is working correctly.) My thinking so far:
Try to use binding to access the RemoveAssetCommand stored in the View Model for the page. However, I couldn't get ancestral binding to work (i.e. trying to find the data context of an element higher up in the XAML hierarchy didn't work because findAncestor isn't a thing in UWP.)
x:Bind looked like a good solution, because it uses an explicit path to the property. So, if I declared ViewModel in my code behind, I could use x:Bind ViewModel.property. All well and good. I did just that, and intellisense allowed me to access the ViewModel.RemoveAssetCommand when typing it out.
However, this did not work, because I get the error no DataType defined for DataTemplate. This makes sense, so I tried two things.
x:DataType="Models:Asset" (put in the DataTemplate tag above) is the model being shown in the data template, so I tried that first. Of course, the command is not declared in the model, it's declared in the View Model, so that didn't work.
I instead tried x:DataType="ViewModels:FamilifyViewModel", thinking I could just use x:Bind with that. However, I then got an error that it couldn't cast an object of type Asset to FamilifyViewModel. This makes sense, because the object getting passed to this data template is of the type Asset.
This is a pain, because the whole reason I thought x:Bind would work is that I could just access the property directly from the ViewModel in the codebehind.
Explicitly stated, 1) is it possible to use x:Bind within a data template to access a base level property (in this case, a Prism command) on the ViewModel? and 2) is there a better way to go about implementing this functionality?
Is it possible to use x:Bind within a data template to access a base level property (in this case, a Prism command) on the ViewModel?
Yes, if you want to access a base level, you can reassign DataContext of button like following:
<Button DataContext="{Binding ElementName=Familily, Path=DataContext}"/>
The Family is the name of UserControl.
is there a better way to go about implementing this functionality?
When you put commad in the ViewModel and bind the button as above. The the bind item of button will become Family DataContext. So you could not invoke delete action directly in the ViewModel.
The best practice to implement this functionality is that put the RemoveAssetCommand in the Asset class. And use the ItemsSource of ListView as Button CommandParameter.
<Button
Command="{Binding RemoveAssetCommand}"
CommandParameter="{Binding ElementName=MyListView, Path=ItemsSource}"
Content=""
FontFamily="Segoe MDL2 Assets"
Grid.Column="4">
</Button>
Asset.cs
public class Asset
{
public string number { get; set; }
public string type { get; set; }
public ICommand RemoveAssetCommand
{
get
{
return new CommandHandler<ObservableCollection<Asset>>((item) => this.RemoveAction(item));
}
}
private void RemoveAction(ObservableCollection<Asset> items)
{
items.Remove(this);
}
}
ViewModel.cs
public class FamilifyViewModel
{
public ObservableCollection<Asset> Assets = new ObservableCollection<Asset>();
public FamilifyViewModel()
{
Assets.Add(new Asset { number = "100001", type = "hello" });
Assets.Add(new Asset { number = "100001", type = "hello" });
Assets.Add(new Asset { number = "100001", type = "hello" });
Assets.Add(new Asset { number = "100001", type = "hello" });
}
}
I'm trying to figure out the RepeaterView in Xamarin Forms Labs.
THE BACKGROUND
I have a simple entity called Entry that has property called Notes
class Entry
{
public string Notes { get; set; }
}
In the common PCL, I've inherited from the RepeaterView to make it non-generic.
class EntryRepeater : Xamarin.Forms.Labs.Controls.RepeaterView<MyKids.Core.Entities.Entry>
{
}
And the XAML of the page where I'm trying to use the repeater looks like this
<rep:EntryRepeater ItemsSource="{Binding Entries}">
<rep:EntryRepeater.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout>
<Label Text="{Binding Notes}" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</rep:EntryRepeater.ItemTemplate>
</rep:EntryRepeater >
where Entries is an ObservableCollection.
THE ISSUE
The problem is that the binding in the DataTemplate seems to point to the main model and not to one item in the collection. BUT it is repeated the correct number of times.
[0:] Binding: 'Notes' property not found on 'MyApp.ViewModels.EntryListViewModel', target property: 'Xamarin.Forms.Label.Text'
To circle back to this question, this was actually an error in earlier versions of Xlabs. It's now fixed and the code above does work.
I'm trying to load a listbox with a list of items being pulled from an OData web service. The data fetch is working well and I'm getting the list of items. The listbox actually works and displays the items, just not every time I start the application... Due to the requirement for the data to be pulled asynchronously sometimes the listbox loads up before the data has returned. When it does I feed it a list with a single 'empty' item to indicate that the data is still loading. Seconds later the data has loaded and I raise a PropertyChanged event for the list. My breakpoint in the list property triggers and when I check the list contains the correct items. But the listbox doesn't display the new items, only the old 'empty' item. It seems exceptionally odd to me that the xaml is clearly requesting the list but then doesn't refresh the layout for the new items.
First the code initialising the ViewModel. ModelReferenceMap implements INotifyPropertyChanged and so should be updating the view when OnPropertyChanged("Areas"); is called (this triggers a fetch of the list from the property but doesn't update the view).
public ModelReferenceMap(Uri serviceURI)
{
// Try initialising these lists to a non null but empty list
// in the hope it will stop the lists breaking when the service
// is a little bit slow...
areas = new List<ModelReferenceItem> { new ModelReferenceItem(null) };
// This is a ServiceReference entity context which will retrieve the data from the OData service
context = new LiveEntities(serviceURI);
// SendingRequest adds credentials for the web service
context.SendingRequest += context_SendingRequest;
// The query to retrieve the items
var areaQuery = from i in context.MigrationItems where i.FusionPTID == 0 && i.Type == "AreaType" orderby i.Name select i;
// On completion this asynccallback is called
AsyncCallback ac = iasyncResult =>
{
// Populates the List with the data items
areas = (from i in ((DataServiceQuery<MigrationItem>) areaQuery).EndExecute(iasyncResult)
select new ModelReferenceItem(i)).ToList();
foreach (ModelReferenceItem area in areas)
{
if (selectedArea == null)
selectedArea = area;
area.PropertyChanged += referenceItem_PropertyChanged;
}
// The Xaml Listbox has its ItemsSource bound to the Areas property. This should trigger a refresh of the listbox contents shouldn't it?
OnPropertyChanged("Areas");
OnPropertyChanged("SelectedArea");
};
// Start the query
((DataServiceQuery<MigrationItem>)areaQuery).BeginExecute(ac, null);
}
Now the XAML. Note that the DataContext of the listbox is ReferenceMap (a property on my main ViewModel which exposes a singleton instance of ModelReferenceMap). I've then bound the ItemsSource to Areas.
<ListBox Grid.Row="0" DataContext="{Binding ReferenceMap}" ItemsSource="{Binding Areas}" SelectedItem="{Binding SelectedArea, Mode=TwoWay}" HorizontalAlignment="Stretch" Margin="3" Name="listBoxFusionAreas" VerticalAlignment="Stretch">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="{Binding CompleteStatusColour}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock FontSize="12" Text="{Binding Name}" />
<TextBlock Grid.Column="1" FontSize="12" Text="{Binding Count}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The Areas property is triggering correctly indicating the binding is working. When Areas is only requested AFTER the service data has been retrieved (ie only once) the list works perfectly. If however the Areas property is triggered prior to the service data returning (ie with the single 'empty' item) it triggers again during the OnPropertyChanged("Areas"); call with the full set of items, only this time the list still just shows the original 'empty' item.
What am I doing wrong?
Whenever you are binding to a collection in your ViewModel you need to make sure whether the items in your collection are gonna change?? In your case you need to implement
ObservableCollection<ModelReferenceItem> areas ;
Instead of
List<ModelReferenceItem> area;
ObservableCollection implements INoifyCollectionChanged event that notifies your view about the changes in the collection (Add/Remove)