Been looking for an example online about this but can't seem to find one. Basically, i would like to use a FFImageLoading:CachedImage in a ListView with the source bound to a string with a url e.g. http://blah.com/image.jpg. This is what i have tried thus far but no image seems to be loaded.
<ListView IsVisible="true" ItemsSource="{x:Static local:viewmodel.TSViewModel.myArticles}">
<ListView.ItemTemplate >
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Vertical">
<ffimageloading:CachedImage
HorizontalOptions="Center" VerticalOptions="Center"
WidthRequest="95" HeightRequest="95"
DownsampleToViewSize="true"
Source = "{Binding ImageUrl}">
</ffimageloading:CachedImage>
<Label Text="{Binding Title}" LineBreakMode="WordWrap" FontSize="Small"/>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If there is something i'm doing wrong or i'm just not supposed to use it this way or there are better alternatives, please help me out. Also, i'm a beginner with xamarin forms and mvvm. Any help would be greatly appreciated.
Update: So the images do load but only once I move away from the images page to another page then back to the images page. The images are being loaded in a content page within a tabbed page so maybe that has something to do with it. Any ideas on how to load them the first time the tabbed page is opened?
HomePage.xaml.cs
using TestApp.view;
using Xamarin.Forms;
using TestApp.database;
namespace TestApp
{
public partial class HomePage : TabbedPage
{
public HomePage(TestAppDatabase db)
{
InitializeComponent();
Children.Add(new Bakeries(db));
Children.Add(new Bars());
Children.Add(new Butcheries());
Children.Add(new Restaurants());
Children.Add(new Supermarkets());
Children.Add(new Entertainment());
}
}
}
Bakeries.xaml.cs
using System.Collections.Generic;
using TestApp.model;
using TestApp.database;
using Xamarin.Forms;
namespace TestApp.view
{
public partial class Bakeries : ContentPage
{
public Bakeries(TestApp db)
{
InitializeComponent();
List<Eateries> bakeries = (List<Eateries>) db.GetBakeries();
bakeriesList.ItemsSource = bakeries;
}
}
}
According to the API docs located here. https://github.com/molinch/FFImageLoading/wiki/Xamarin.Forms-API
You must add this line to your platform specific project (AppDelegate.cs, MainActivity.cs, etc) before you use FFImageLoading:
CachedImageRenderer.Init();
Related
I have the following code which used to work on Android just fine until MAUI Preview 9:
XAML:
<CollectionView ItemsSource="{Binding ViewModel_MyConns}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding LastName}" TextColor="Red"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
MVVM code:
public class ConnsViewModel: INotifyPropertyChanged {
public ObservableCollection<STuser> ViewModel_MyConns { get; }
IList locallist;
// constructor:
public ConnsViewModel() {
ViewModel_MyConns = new ObservableCollection<STuser>();
locallist = ReadDataFromDB();
foreach (var item in locallist) {
ViewModel_MyConns.Add(item);
}
}
}
Everything used to work fine and I'd get the CollectionView updated with the values I read from the DB.
But now with MAUI Preview 9 this no longer works.
Any ideas/theories/solutions?
Thanks.
(P.S. Since I'm working with an ObservableCollection, there's no need to raise the PropertyChanged event. But in a desperate attempt to make it work, I fired PropertyChanged all the same, but to no avail).
I'm trying to make a custom home page where pages are listed on an Horizontal scroll view as "Services" so each one of them should navigate to a different Page.
I have a view like this:
<controls:HorizontalScrollView HeightRequest="160"
Orientation="Horizontal"
ItemsSource="{Binding OwnerServicesList}"
x:Name="OwnerServicesSlider"
ItemSelected="OwnerServicesSlider_ItemSelected">
<controls:HorizontalScrollView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="10,0,5,0" WidthRequest="100" HeightRequest="100">
<Image HorizontalOptions="Start" Source="{Binding ImgUrl}" WidthRequest="100" HeightRequest="100" />
<Label Style="{StaticResource BoldLabel}" HorizontalTextAlignment="Center" FontSize="13" LineBreakMode="TailTruncation" Text="{Binding Name}" TextColor="Black"/>
</StackLayout>
</ViewCell>
</DataTemplate>
Im using a custom made controller for a Horizontal Scroll view that works like a listview, every item on tap raises a ItemTappedEventArgs event.
Inside my model i´ve declared a public Page Page { get; set; } for each object in the scroll view.
What im trying to do is recover the tapped element and recover the Page stored in it so that I can Navigate to that specific page.
So far I have something like this:
private void OwnerServicesSlider_ItemSelected(object sender, ItemTappedEventArgs e)
{
var service = OwnerServicesSlider.SelectedItem as Services;
Navigation.PushAsync(service.Page);
}
It shows no errors but when I run it I get a
InvalidOperationException: 'Page must not already have a parent.
Any hint will be appreciated!
as Jason said,maybe the page you would push which is exist in thecurren navigation structure,there is a workaround ,before you push the page :
private void OwnerServicesSlider_ItemSelected(object sender, ItemTappedEventArgs e)
{
var service = OwnerServicesSlider.SelectedItem as Services;
service.Page.Parent = null;
Navigation.PushAsync(service.Page);
}
Update: Just a reminder, there's a 500 point bonus on this if someone can just show me how to implement this functionality without using Gestures>
I am using a ViewCell and a gesture recognizer to open up a picker with the following code. The ViewCell has a label on the left and a label area on the right that is populated initially when the app starts and later with the picker when the ViewCell is clicked.
XAML
<ViewCell x:Name="ati" Tapped="OpenPickerCommand">
<Grid VerticalOptions="CenterAndExpand" Padding="20, 0">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding OpenPickerCommand}"
CommandParameter="{x:Reference atiPicker}" NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
<local:LabelBodyRendererClass Text="Answer Time Interval" HorizontalOptions="StartAndExpand" />
<Picker x:Name="atiPicker" IsVisible="false" HorizontalOptions="End" SelectedIndexChanged="atiPickerSelectedIndexChanged" ItemsSource="{Binding Times}"></Picker>
<local:LabelBodyRendererClass x:Name="atiLabel" HorizontalOptions="End"/>
</Grid>
</ViewCell>
<ViewCell x:Name="pti" Tapped="OpenPickerCommand">
<Grid VerticalOptions="CenterAndExpand" Padding="20, 0">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding OpenPickerCommand}"
CommandParameter="{x:Reference ptiPicker}" NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
<local:LabelBodyRendererClass Text="Phrase Time Interval" HorizontalOptions="StartAndExpand" />
<Picker x:Name="ptiPicker" IsVisible="false" HorizontalOptions="End" SelectedIndexChanged="ptiPickerSelectedIndexChanged" ItemsSource="{Binding Times}"></Picker>
<local:LabelBodyRendererClass x:Name="ptiLabel" HorizontalOptions="End"/>
</Grid>
</ViewCell>
C# This works for different pickers (ati, bti, pti etc) with CommandParameter
public SettingsPage()
{
InitializeComponent();
BindingContext = new CommandViewModel();
}
void atiPickerSelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
if (selectedIndex != -1)
{
App.DB.UpdateIntSetting(Settings.Ati, selectedIndex);
atiLabel.Text = AS.ati.Text();
}
}
void ptiPickerSelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
if (selectedIndex != -1)
{
App.DB.UpdateIntSetting(Settings.Pti, selectedIndex);
ptiLabel.Text = AS.pti.Text();
}
}
public class CommandViewModel: ObservableProperty
{
public ICommand openPickerCommand;
public CommandViewModel()
{
openPickerCommand = new Command<Picker>(PickerFocus);
//openPickerCommand = new Command(tapped);
}
public ICommand OpenPickerCommand
{
get { return openPickerCommand; }
}
void PickerFocus(Picker param)
{
param.Focus();
}
}
I would like to remove the use of TapGestureRecognizers but I still want to retain the functionality and layout.
It's been suggested to me that it would be better if I used the Tapped event of the ViewCell like this:
Tapped="OnTapped"
Can someone explain in some detail how I could wire this up in C#. Would I be best to code something into the CommandViewModel as well as in the C# backing code. Also can the view model have one method that takes an argument so it could be used to open up different pickers?
An example of how I could do this would be very much appreciated. Note that I don't particularly need to use the CommandViewModel if there is a way that I could do this by coding just in the .cs backing code.
(Sorry for the poor english)
Despite not being best practice, I guess you can do something like this, dismissing the viewmodel:
XAML:
<ViewCell x:Name="ati" Tapped="OpenPickerCommand">
<Grid VerticalOptions="CenterAndExpand" Padding="20, 0">
<local:LabelBodyRendererClass Text="Answer Time Interval" HorizontalOptions="StartAndExpand" />
<Picker x:Name="atiPicker"
IsVisible="false"
HorizontalOptions="End"
SelectedIndexChanged="atiPickerSelectedIndexChanged"
ItemsSource="{Binding Times}">
</Picker>
<local:LabelBodyRendererClass x:Name="atiLabel" HorizontalOptions="End"/>
</Grid>
</ViewCell>
<ViewCell x:Name="pti" Tapped="OpenPickerCommand">
<Grid VerticalOptions="CenterAndExpand" Padding="20, 0">
<local:LabelBodyRendererClass Text="Phrase Time Interval" HorizontalOptions="StartAndExpand" />
<Picker x:Name="ptiPicker" IsVisible="false" HorizontalOptions="End" SelectedIndexChanged="ptiPickerSelectedIndexChanged" ItemsSource="{Binding Times}"></Picker>
<local:LabelBodyRendererClass x:Name="ptiLabel" HorizontalOptions="End"/>
</Grid>
</ViewCell>
C#:
private void OpenPickerCommand(object sender, System.EventArgs e)
{
if (sender != null)
{
Picker pkr = sender == ati ? atiPicker : ptiPicker;
pkr.Focus();
}
}
Answering your question "Can the view model have one method that takes an argument?", it is exactly what you're already doing using the 'OpenPickerCommand' method. The problem is that using the ViewCell's public event 'Tapped', you can't set parameters to the delegate handler.
Let me know if it works for you or if you do need some more information.
I hope it helps.
You can solve this with attached properties. Simply define a "behavior" class for ViewCell that adds the Command/Parameter properties.
public static class TappedCommandViewCell
{
private const string TappedCommand = "TappedCommand";
private const string TappedCommandParameter = "TappedCommandParameter";
public static readonly BindableProperty TappedCommandProperty =
BindableProperty.CreateAttached(
TappedCommand,
typeof(ICommand),
typeof(TappedCommandViewCell),
default(ICommand),
BindingMode.OneWay,
null,
PropertyChanged);
public static readonly BindableProperty TappedCommandParameterProperty =
BindableProperty.CreateAttached(
TappedCommandParameter,
typeof(object),
typeof(TappedCommandViewCell),
default(object),
BindingMode.OneWay,
null);
private static void PropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is ViewCell cell)
{
cell.Tapped -= ViewCellOnTapped;
cell.Tapped += ViewCellOnTapped;
}
}
private static void ViewCellOnTapped(object sender, EventArgs e)
{
if (sender is ViewCell cell && cell.IsEnabled)
{
var command = GetTappedCommand(cell);
var parameter = GetTappedCommandParameter(cell);
if (command != null && command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
}
public static ICommand GetTappedCommand(BindableObject bindableObject) =>
(ICommand)bindableObject.GetValue(TappedCommandProperty);
public static void SetTappedCommand(BindableObject bindableObject, object value) =>
bindableObject.SetValue(TappedCommandProperty, value);
public static object GetTappedCommandParameter(BindableObject bindableObject) =>
bindableObject.GetValue(TappedCommandParameterProperty);
public static void SetTappedCommandParameter(BindableObject bindableObject, object value) =>
bindableObject.SetValue(TappedCommandParameterProperty, value);
}
After that reference your behavior namespace in XAML and specify the property values using fully qualified names:
<ViewCell StyleId="disclosure-indicator"
behaviors:TappedCommandViewCell.TappedCommand="{Binding BrowseCommand}"
behaviors:TappedCommandViewCell.TappedCommandParameter="https://www.google.com">
<StackLayout Orientation="Horizontal">
<Label Text="Recipient"
VerticalOptions="Center"
Margin="20,0"/>
<Label Text="{Binding LedgerRecord.Recipient}"
HorizontalOptions="EndAndExpand"
VerticalOptions="Center"
Margin="0,0,20,0"/>
</Label>
</StackLayout>
</ViewCell>
The above will allow you to use MVVM and no Tap Gesture Recognizers.
The first problem is that you're mixing the code-behind and MVVM
approaches in the same code. It is confusing and certainly not the
right way to code what you want to achieve. So, all commanding must
be in the ViewModel attached to the View, no code-behind apart some
code only used for UI effects.
There is no need to define a gesture recognizer for all visual items since you just want to detect the tap on all the surface of the viewcell. To achieve this you must define all children of the ViewCell with InputTransparent=true. So the tap will not be detected and will be trapped by the parent ViewCell (you
must indicate the InpuTransparent because there is no tap event
bubbling in X.Forms).
Showing and Hidding the picker is a View problem not a ViewModel one. So here you can use some code-behind to create an event handler for the ViewCell Tapped event. This handler will just set visible=true on the picker.
The picker selected event must be connected to a corresponding Command in the ViewModel. So each time the picker is displayed and a value is selected your viewmodel will be aware of the action. This is the only command you need in your viewmodel. Depending of XForms version the picker has no bindable command, so you can use one of the numerous "bindablepicker" implementation you can find on the web or you can also use a XAML EventToCommand Behavior.
So there is two different problems : showing/hidding the picker which can be achieved directly in XAML or with the help of a bit of code-behind; and the picker item selection that must be managed using a Command in the viewmodel.
Hoping this will help you
I'm trying to save a file to the device.
The problem is that I'm hardcoding the filename from code behind.
My requirement is to ask the user to save a file with a user defined filename. How can I ask the user to save a file by opening a filesave dialog in Xamarin.Forms?
Have you solved it? Can you post your solution in case?
This question could fit your requirements, it uses this plugin: Xam.plugin.filepicker Xam.Plugin.FilePicker Works fine but can't get file
Here is the easiest popup page using Rg.Plugins.Popup
cs:
public partial class PromptPopup : PopupPage
{
public event EventHandler Oked;
public event EventHandler Canceled;
public PromptPopup(string title, string text)
{
InitializeComponent();
PopupText.Text = text;
PopupTitle.Text = title;
}
private void OnCancel(object sender, EventArgs e)
{
this.Canceled(sender, e);
PopupNavigation.PopAsync(false);
}
private void OnOk(object sender, EventArgs e)
{
this.Oked(sender, e);
PopupNavigation.PopAsync(false);
}
}
its xaml:
<StackLayout VerticalOptions="Center" HorizontalOptions="FillAndExpand" Padding="20, 20, 20, 20">
<StackLayout BackgroundColor="White" Padding="10, 10, 10, 10">
<Label x:Name="PopupTitle" Text="PromptPopupTitle" TextColor="Gray" FontSize="20" HorizontalOptions="Center" />
<ScrollView>
<StackLayout>
<StackLayout Orientation="Horizontal">
<Label x:Name="PopupText"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Center"
TextColor="Gray"></Label>
</StackLayout>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Button Text="{i18n:Translate PopupButtonCancel}" Clicked="OnCancel" HorizontalOptions="FillAndExpand"></Button>
<Button Text="{i18n:Translate PopupButtonOk}" Clicked="OnOk" HorizontalOptions="FillAndExpand"></Button>
</StackLayout>
</StackLayout>
</ScrollView>
</StackLayout>
(this page comes from a PopupPage instead of ContentPage)
and you can call it like this
var page = new PromptPopup("title", "text");
page.Oked += Page_OkedLogout;
page.Canceled += Page_CanceledLogout;
await PopupNavigation.PushAsync(page);
further deep support: https://github.com/rotorgames/Rg.Plugins.Popup
You have to implement the file picker yourself, there's no built in way to do that, best to go out to GitHub and look around if that's what you're after.
It sounds to me like you could easily achieve what you're after by just popping a text box, having a user enter a file name, validate the filename with a regex or something and then just saving the file with that file name, you could even just save the file and give them the option to rename the file by listing them all out, but that's all too broad for me to give you an exact implementation, so the best I can do for you is tell you the simple way to do what you're wanting to do.
After selecting file you just open Popup with "Entry" and save file using Entry Text. you can open popup using "Rg.Plugins.Popup"
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.