I can't believe I have spent a day trying to get this to work! I began developing in Xamarin.Forms last month and for the most part things have been going pretty smooth. My background is ASP.Net MVC and Windows Forms so the MVVM concept is new to me.
The issue that I am having is very frustrating and seems silly really. I imagine it could be my implementation possibly because I am not using MVVM but am performing all binding in the code behind? I have a page that has a search box and a ListView controll. Each of the items in the ListView is fairly simple... it only consists of a few labels and a switch. The ListView is bound every time someone performs a search. The problem that I am having is that every time someone performs a search it refreshes the Model everyting is bound to... this in turn triggers the Switches Toggle Event. Within the Toggle Event I have code that would update the database when the value is changed so I do not want this fired off every time the model changes. It is very frustrating as there does not appear to be a Click Event. See Xaml and the Method I am using below:
public async void SearchItems()
{
PageLoaded = false;
if(string.IsNullOrEmpty(this.txtSearch.Text)) { return; }
this.LoadSearch.IsRunning = true;
// Populate Locations For Item
string ItemType = HttpUtility.UrlEncode(this.SelectItemType.SelectedItem.ToString());
string Search = HttpUtility.UrlEncode(this.txtSearch.Text.ToLower());
string url = string.Format("https://test.com/api/LocationsSearch?itemType={0}&search={1}", ItemType, Search);
LocationsList = new List<Locations>();
var json = await func.GetIBApi(url);
LocationsList = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Locations>>(json);
this.ItemsList.ItemsSource = LocationsList;
this.LoadSearch.IsRunning = false;
PageLoaded = true;
}
Toggle Event:
private async void Switch_Toggled(object sender, ToggledEventArgs e)
{
try
{
if (PageLoaded)
{
bool isPrimary = e.Value;
ViewCell cell = (sender as Switch).Parent.Parent.Parent as ViewCell;
var location = cell.BindingContext as Locations;
string display = "";
if (isPrimary)
{
display = "Are you sure you want to make this the 'Primary Location'?";
}
else
{
display = "Are you sure you want to unset this as the 'Primary Location'? Note: Make sure you set another location as the 'Primary' on this PartNumber!";
}
var result = await App.Current.MainPage.DisplayAlert("Primary Change Confirmation", display, "Yes", "No");
if (result)
{
string url = string.Format("https://test.com/api/LocationsSearch?partNumber={0}&location={1}&isPrimary={2}", location.ID, location.Location, isPrimary);
var json = await func.PostIBApi(url);
await Navigation.PopModalAsync();
}
}
}
catch (Exception e2)
{
}
}
XAML:
<?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="IBMobile.Views.LocationsPage"
xmlns:local2="clr-namespace:FontAwesome"
Visual="Material">
<ContentPage.IconImageSource>
<FontImageSource FontFamily="{StaticResource FontAwesomeSolid}" Glyph="{x:Static local2:IconFont.MapMarkerAlt}" />
</ContentPage.IconImageSource>
<ContentPage.Content>
<StackLayout Padding="25">
<Picker x:Name="SelectItemType" SelectedIndexChanged="SelectItemType_SelectedIndexChanged" />
<SearchBar x:Name="txtSearch" SearchButtonPressed="TxtSearch_SearchButtonPressed" Placeholder="Search..." FontSize="Title"></SearchBar>
<ActivityIndicator Color="#007D5D" IsRunning="false" x:Name="LoadSearch" HorizontalOptions="Center" />
<ListView x:Name="ItemsList" ItemTapped="ItemsList_ItemTapped" HasUnevenRows="True" BackgroundColor="White">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout VerticalOptions="FillAndExpand">
<Grid VerticalOptions="FillAndExpand" Padding="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"></ColumnDefinition>
<ColumnDefinition Width="3*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label TextColor="#007D5D" Text="{Binding ViewID}" FontSize="Title" VerticalTextAlignment="Center" Grid.Column="0" Grid.Row="0"></Label>
<Label TextColor="#007D5D" Text="{Binding Location}" FontSize="Title" VerticalTextAlignment="Center" Grid.Column="1" Grid.Row="0"></Label>
<Label Text="Primary" Grid.Column="2" Grid.Row="0"></Label>
<Switch IsToggled="{Binding IsPrimary}" x:Name="Toggle1" ThumbColor="#007D5D" Grid.Column="2" Grid.Row="1" Toggled="Switch_Toggled"/>
<Label Text="{Binding Description}" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2"></Label>
<Label Text="{Binding Message}" Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="3" FontSize="Small" TextColor="Red"></Label>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
if (PageLoaded)
{
//entered handler
bool isPrimary = e.Value; //we are invoking the handler again from inside itsself
// all the mess comes from this
Use another lock:
if (PageLoaded && !_internal)
{
//entered handler
_internal=true;
bool isPrimary = e.Value;
_internal=false;
Related
I have a login screen that I want a activitymonitor to show up when I click the button. But I click the button and it doesn't show not real sure why.
Here is C#
On button click
activityIndicator.IsVisible = true;
WebApiService ws = new WebApiService();
var response = await ws.LoginAsync(Username.Text, Password.Text, UUID);
activityIndicator.IsVisible = false;
if (response.ResultCode == AuthenticationResultCodeEnum.SUCCESS)
{
// do something with the result
}
}
Here is my Xaml
<StackLayout >
<Label x:Name="Version" HeightRequest="50" VerticalTextAlignment="Start" HorizontalTextAlignment="End" Margin="0,30,20,0">
<Label.GestureRecognizers>
<TapGestureRecognizer
Tapped="OnTapGestureRecognizerTapped"
NumberOfTapsRequired="2" />
</Label.GestureRecognizers>
</Label>
<Image Source="LEDShield_Small.png" Margin="0,0,0,30"></Image>
<StackLayout Padding="20,0,20,0">
<Label Text="MI DNR Username" FontAttributes="Bold"></Label>
<Entry x:Name="Username" Placeholder="Username" IsSpellCheckEnabled="False" IsTextPredictionEnabled="False"></Entry>
<Label Text="MI DNR Password" FontAttributes="Bold"></Label>
<Entry x:Name="Password" Placeholder="Password" IsPassword="True"></Entry>
<Button x:Name="SigninButton" Text="Sign In" IsEnabled="False" HeightRequest="50"></Button>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*" />
<RowDefinition Height="200" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackLayout Grid.Row="0" Grid.Column="1" Orientation="Horizontal" HorizontalOptions="End">
<Label x:Name="RememberMeLabel" HeightRequest="10" Margin="0" VerticalTextAlignment="Center" Text="Keep Me Signed In">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding RememberMeLabelCommand}" />
</Label.GestureRecognizers>
</Label>
<CheckBox HeightRequest="50" VerticalOptions="Center" x:Name="RememberMe" IsChecked="{Binding IsRememberMeChecked}" Color="Green"></CheckBox>
</StackLayout>
<Label Grid.Row="0" Grid.Column="0" Margin="25" Text="Forgot Password" HorizontalOptions="Center" TextDecorations="Underline" >
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" NumberOfTapsRequired="1" />
</Label.GestureRecognizers>
</Label>
</Grid>
</StackLayout>
<Button x:Name="ResetRequestButton" IsVisible="false" Text="This account has been locked. Click here to to send a password reset request."></Button>
<ActivityIndicator IsRunning="true" x:Name="activityIndicator" Color="Green" VerticalOptions="Center" />
</StackLayout>
I use this class for ActivityIndicator:
public class ProgressSplash : Grid, IDisposable
{
public static ProgressSplash Show(Layout<View> parent)
{
return new ProgressSplash(parent);
}
ActivityIndicator ai;
Layout<View> parent;
private ProgressSplash(Layout<View> parent)
{
this.parent = parent;
HorizontalOptions = LayoutOptions.Fill;
VerticalOptions = LayoutOptions.Fill;
BackgroundColor = Color.FromHex("#40000000");
ai = new ActivityIndicator();
ai.HorizontalOptions = Device.RuntimePlatform == Device.UWP ? LayoutOptions.Fill : LayoutOptions.CenterAndExpand;
ai.VerticalOptions = LayoutOptions.CenterAndExpand;
ai.Opacity = 1;
if (parent is Grid)
{
if (((Grid)parent).RowDefinitions.Count > 1)
Grid.SetRowSpan(this, ((Grid)parent).RowDefinitions.Count);
if (((Grid)parent).ColumnDefinitions.Count > 1)
Grid.SetColumnSpan(this, ((Grid)parent).ColumnDefinitions.Count);
}
parent.Children.Add(this);
Children.Add(ai);
ai.IsVisible = true;
ai.IsRunning = true;
}
public void Dispose()
{
ai.IsRunning = false;
ai.IsVisible = false;
if (parent.Children.Contains(this))
parent.Children.Remove(this);
}
}
And there is how to use it:
using (var prc = ProgressSplash.Show(grdMain))
{
// ... do something slow
}
Where "grdMain" is main container on the page, which is Grid (it works with it only).
I'm not sure where ActivityIndicator tries to show, when inside a StackLayout.
Consider using it this way:
<Grid ...>
<StackLayout ...>
...
</StackLayout ...>
<ActivityIndicator x:Name="activityIndicator" IsRunning="False"
...
VerticalOptions="Center" />
</Grid>
In button click:
activityIndicator.IsRunning = true;
EXPLANATION:
The layout is a single-cell grid, with two children laid on top of each other.
First child is your "normal" content, inside a StackLayout.
Second child is ActivityIndicator, initially not running (so not visible).
When you set IsRunning to true, it shows up.
TECH NOTE: The reason to do this with IsRunning instead of IsVisible, is to avoid forcing page to perform a layout. OTOH, I've often done it (and seen it done by others), with code that toggles BOTH IsVisible and IsRunning. So maybe performance is a non-issue.
Regardless, by default it starts IsVisible=true, IsRunning=false, so might as well just toggle IsRunning.
I have this Task which bring data from firebase :
public async Task<List<Player>> GetPlayersData()
{
var playersData = (await client.Child("Players")
.OnceAsync<Player>())
.Select(f => new Player
{
PlayerName = f.Object.PlayerName,
PlayerTime = f.Object.PlayerTime,
PlayerRank = f.Object.PlayerRank
}).ToList();
return playersData;
}
it worked perfectly , but i want to get the (playersData) to put it as "ItemSource" in "Listview" in XAML, so i call the task in the page constructor :
public RankTable()
{
InitializeComponent();
BindingContext = this;
client = new FirebaseClient("https://numbergame-*****firebaseio.com/");
GetPlayersData();
}
Now it will run the task and brings the players data but I don't know how to control it and put it as ItemSource in the Following XAML code :
<StackLayout>
<CollectionView Grid.ColumnSpan="2" Grid.RowSpan="2" SelectionMode="None" ItemsSource="{Binding ??}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame HasShadow="True" BackgroundColor="Beige" CornerRadius="15"
IsClippedToBounds="True" Visual="Material" InputTransparent="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Text="{Binding PlayerRank}" FontSize="15" Grid.Column="1"/>
<Label Text="{Binding PlayerName}" FontSize="15" Grid.Column="2"/>
<Label Text="{Binding PlayerTime}" FontSize="15" Grid.Column="3"/>
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
any help please and sorry for my bad english..
<CollectionView x:Name="Players" ... /
// GetPlayersData is async so you can't call it from the constructor
protected override void OnAppearing()
{
Players.ItemsSource = await GetPlayersData();
}
wondering if anybody can help with a solution I am looking for. In my Xamarin project, I have a 'add another line' button which adds another stack layout underneath it with the same controls as the first one. I want to save the new added one to JSON but not sure where to start, has anybody done this before?
I have added the first stack to json but i need it to reckonise the new ones on the button press. (sorry if it does not make sense)
Page.xaml Code:
<Grid Margin="0, 30, 0, 30">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label x:Name="NameLabel" Text="Name" TextColor="Black" FontSize="20" Grid.Column="0" Grid.Row="0" />
<Label x:Name="ArrivalLabel" Text="Arrival" TextColor="Black" FontSize="20" Grid.Column="1" Grid.Row="0" />
<Label x:Name="DescriptionLabel" Text="Description" TextColor="Black" FontSize="20" Grid.Column="2" Grid.Row="0" />
<Label x:Name="DepartLabel" Text="Depart" TextColor="Black" FontSize="20" Grid.Column="3" Grid.Row="0" />
<Label x:Name="SignLabel" Text="Sign" TextColor="Black" FontSize="20" Grid.Column="4" Grid.Row="0" />
</Grid>
<StackLayout x:Name="AddMoreNotes">
<StackLayout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Entry x:Name="Name" FontSize="20" Grid.Column="0" Grid.Row="0" />
<TimePicker x:Name="Arrival" FontSize="20" Grid.Column="1" Grid.Row="0" />
<Entry x:Name="Description" FontSize="20" Grid.Column="2" Grid.Row="0" />
<TimePicker x:Name="Depart" FontSize="20" Grid.Column="3" Grid.Row="0" />
<Entry x:Name="Sign" FontSize="20" Grid.Column="4" Grid.Row="0"/>
</Grid>
</StackLayout>
<!--Add More View Spawns Here Do Not Change-->
</StackLayout>
<StackLayout Orientation="Horizontal" HorizontalOptions="End">
<Button x:Name="AddMoreButton" Text="Add More" FontSize="16" BorderRadius="6" Clicked="AddMoreButton_Clicked" />
</StackLayout>
Page.xaml.cs Code:
private void AddMoreButton_Clicked(object sender, EventArgs e)
{
AddMoreNotes.Children.Add(new AttendanceAddMoreView()
{
});
}
private void SubmitButton_Clicked(object sender, EventArgs e)
{
var attendanceChecklist = new AttendanceChecklist();
var AttentionOf1 = new List<AttentionOf>();
var AttendanceSection1 = new List<AttendanceSection>();
attendanceChecklist.TheDate = TheDate.Date;
attendanceChecklist.AttendanceNumber = AttendanceNumber.Text;
attendanceChecklist.EmployeeName = EmployeeName.Text;
AttentionOf1.Add(new AttentionOf()
{
FirstName = FirstName.Text,
LastName = LastName.Text,
AddressLine1 = AddressLine1.Text,
AddressLine2 = AddressLine2.Text,
CityOrTown = CityOrTown.Text,
County = County.Text,
Postcode = Postcode.Text,
ContractTitleOrRef = ContractTitleOrRef.Text,
CustomerName = CustomerName.Text,
SiteContact = SiteContact.Text,
});
AttendanceSection1.Add(new AttendanceSection()
{
Name = Name.Text,
Arrival = Arrival.Time,
Description = Description.Text,
Depart = Depart.Time,
Sign = Sign.Text
});
attendanceChecklist.attentionOf = AttentionOf1;
attendanceChecklist.attendanceSection = AttendanceSection1;
var json = JsonConvert.SerializeObject(attendanceChecklist, Newtonsoft.Json.Formatting.Indented);
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var filename = Path.Combine(documents, "AttendanceNote.json");
File.WriteAllText(filename, json);
}
Edit:
added new code
public string LargerJsonString;
private void AddMoreButton_Clicked(object sender, EventArgs e)
{
AddMoreNotes.Children.Add(new AttendanceAddMoreView()
{
});
StringContent content = new StringContent(JsonConvert.SerializeObject(AddMoreNotes), Encoding.UTF8, "application/json");
LargerJsonString += content;
}
To Start this I have done this in MVVM and hence you might have to use that if you are not already using it.
Now I made a dummy layout and a dummy model which can be seen below:
<?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="XamarinCertUnderstanding.Views.MainPage">
<StackLayout Margin="0, 15, 0, 0">
<Button VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" BorderColor="Black" BackgroundColor="Blue" Command="{Binding AddButton}" TextColor="Green" Text="Add Button"/>
<ListView x:Name="MainList" ItemsSource="{Binding DefaultModels}" BackgroundColor="Transparent" HasUnevenRows="True"
SeparatorVisibility="Default" Margin="0, 15, 0, 0">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout BackgroundColor="White" Spacing="10" >
<Entry Text="{Binding Name,Mode=TwoWay}" Placeholder="Name" FontSize="20" BackgroundColor="White"/>
<TimePicker Time="{Binding Arrival,Mode=TwoWay}" FontSize="20" BackgroundColor="White"/>
<Entry Text="{Binding Description,Mode=TwoWay}" Placeholder="Description" FontSize="20" BackgroundColor="White"/>
<TimePicker Time="{Binding Depart,Mode=TwoWay}" FontSize="20" BackgroundColor="White"/>
<Entry FontSize="20" Text="{Binding Sign,Mode=TwoWay}" Placeholder="Sign" BackgroundColor="White"/>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
I am using BindingMode as TwoWay so that any change on the View is reflected to the ViewModel Collection and Vice versa
The DummyModel that I use is as shown below:
public class DefaultModel : INotifyPropertyChanged
{
private string userName = string.Empty;
private string description = string.Empty;
private string sign = string.Empty;
private TimeSpan arrival = TimeSpan.Zero;
private TimeSpan depart = TimeSpan.Zero;
public string Name
{
get
{
return userName;
}
set
{
userName = value;
RaisePropertyChanged(nameof(Name));
}
}
public TimeSpan Arrival
{
get
{
return arrival;
}
set
{
arrival = value;
RaisePropertyChanged(nameof(Arrival));
}
}
public string Description
{
get
{
return description;
}
set
{
description = value;
RaisePropertyChanged(nameof(Description));
}
}
public TimeSpan Depart
{
get
{
return depart;
}
set
{
depart = value;
RaisePropertyChanged(nameof(Depart));
}
}
public string Sign
{
get
{
return sign;
}
set
{
sign = value;
RaisePropertyChanged(nameof(Sign));
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I am using the INotifyPropertyChanged interface to notify any changes in the property. For better understanding check this:https://learn.microsoft.com/en-us/dotnet/framework/winforms/how-to-implement-the-inotifypropertychanged-interface
In your MainPage.Xaml.cs set the BindingContext to MainViewModel in the constructor.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MainViewModel();
}
}
Now in your ViewModel do the following:
public class MainViewModel : BindableObject
{
public Command AddButton { get; set; }
private ObservableCollection<DefaultModel> defaultModels;
public ObservableCollection<DefaultModel> DefaultModels
{
get { return defaultModels; }
set
{
defaultModels = value;
OnPropertyChanged(nameof(DefaultModels));
}
}
public MainViewModel()
{
DefaultModels = new ObservableCollection<DefaultModel>();
DefaultModels.Add(new DefaultModel());
AddButton = new Command(AddButtonCommand);
}
private void AddButtonCommand(object obj)
{
DefaultModels.Add(new DefaultModel());
}
}
Now how this works is quite simple on AddButton Click I call the AddButtonCommand and I initialize a blank object and add it to the ObservableCollection which then updates my View and adds another set of controls as far as get this data goes you can get all the data from your DefaultModels collection.
Goodluck
Revert in case of queries
Sorry I'm not able to comment so I'll leave my thoughts here: first off I'd use a ListView as it allows you to easily bind items to your ListView. A little example beneath
<ListView x:Name="MainList" ItemsSource="{Binding Messages}" BackgroundColor="Transparent" HasUnevenRows="True" SeparatorVisibility="Default" Margin="0, 15, 0, 0" IsPullToRefreshEnabled="True" RefreshCommand="{Binding RefreshingMessages}" IsRefreshing="{Binding IsRefreshing}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
Put your code in here
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This should work if you're doing this in the MVVM, Model View to View Model, structure which makes life a lot easier. However I don't believe this is a necessity. In the case of you wanting to add another line to your JSON, I see you're already using JsonConvert, so can't you deserialize the the JSON to a list of the object and loop through the list and output a number of items on your screen?
string JsonContent = await WhateverYourJsonIs.Content.ReadAsStringAsync().ConfigureAwait(false);
var something = JsonConvert.DeserializeObject<List<WhateverYourObjectIsCalled>>(JsonContent)
I'm not exactly sure if that's what you're trying to achieve, if not, let me know and I'll try to help! In this case, WhateverYourJsonIs would most likely need to be a string of Json, you might not need that line, in my case of returning information from a server I do need to read it as a string however you may not need to as it may already be a string, that would be a little testing on your part as I haven't done it this way so I've grabbed snips from my code. Hope this helps!
Edit:
If you serialize your object and have a list of string using this method
StringContent content = new StringContent(JsonConvert.SerializeObject(NewItem), Encoding.UTF8, "application/json");
That'll convert it into Json for you, you can then append it on the end of a larger Json string for example:
LargerJsonString += content
And then add it to your File. Alternatively you could use something such as SQLite and store it in a local Database Table? This is the method I use
Edit 2
So I did a little digging on the Docs.Microsoft to try and find what you're using for "File.WriteAllText" and I found this method which appends extra text onto the end of the file
string appendText = "This is extra text" + Environment.NewLine;
File.AppendAllText(path, appendText);
Try using this rather than WriteAll
Im unable to set the selectedItem in a listview control. Ive set the binding context and can debug the code without issue. Onload of the form I can see the get and set method being called but when I select an item in the listview the set is not called. I cannot understand why
private NavigationItems _SelectedNavigationItem;
public NavigationItems SelectedNavigationItem
{
get { return _SelectedNavigationItem; }
set
{
if (value != null)
{
_SelectedNavigationItem = value;
NavigateToPage(value);
}
}
}
public async void NavigateToPage(NavigationItems selectedItem )
{
switch (selectedItem.Id)
{
case "Appointments":
await Navigation.PushAsync(new MyAppointments());
break;
case "Companies":
await Navigation.PushAsync(new Companies());
break;
case "Messages":
await Navigation.PushAsync(new Messages());
break;
default:
break;
}
}
View
<!-- Navigation -->
<ListView Grid.ColumnSpan="2" Margin="20,30,20,10" Grid.Row="1"
ItemsSource="{Binding NavigationCollection}"
SelectedItem="{Binding SelectedNavigationItem}"
HasUnevenRows="false"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<Grid Padding="5,5,5,5" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<!--Icon-->
<Image Aspect="AspectFit" Grid.Column="0" HeightRequest="18" WidthRequest="18" Source="{Binding Icon}"/>
<!--Navigation Name-->
<Label Margin="10,0,30,0" Grid.Column="1" Grid.ColumnSpan="2" Style="{StaticResource SecondaryLabelStyle}" Text="{Binding Name}" FontSize="Medium"/>
<!--Navigation Totals-->
<Frame VerticalOptions="Center" Grid.Column="3" Style="{StaticResource CircleFrameStyle}">
<Label TextColor="#d2d2d2" HorizontalTextAlignment="Center" VerticalOptions="Center" Text="{Binding Total}" FontSize="Small">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ForgotPassword}" />
</Label.GestureRecognizers>
</Label>
</Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Everything appears to be in order but something strange is happening. Ive also tried setting mode=TwoWay explicitly in the xaml but I know listview mode is twoway by default.
Kevin
I think doing your setter code this way will solve your problems
private NavigationItems _SelectedNavigationItem;
public NavigationItems SelectedNavigationItem
{
get { return _SelectedNavigationItem; }
set
{
if (_SelectedNavigationItem != value)
{
_SelectedNavigationItem = value;
NavigateToPage(value);
}
}
}
I have a Xamarin.Forms PCL VS2015 Solution and have spent three days searching for a solution to my problem. I have tried numerous ways including using the Listview onitemselected which works fine for Win10 but not for Android or iOS. Tried this.FindByName(emailValue); and using the Sender Frame also but no luck. I need to provide the email address which is the label text binding.
<Label x:Name="emailValue"
Text="{Binding Email}"/>
The label is a child of a child of a listview item.
The x:Name element is not available in the code behind. I understand this is because the label is in a listview and the compiler would not be able to distinguish between the listview items.
I have drilled down using intellisense, on the sender of the Tap ( the Frame) and I can see the Email text that I need but I cant work out a way of extracting it.
<StackLayout Orientation="Vertical"
VerticalOptions="FillAndExpand">
<ListView x:Name="listViewContacts"
ItemSelected="OnListViewItemSelected"
IsPullToRefreshEnabled="true" Refreshing="OnRefresh"
SeparatorColor="#FFCF00"
VerticalOptions="FillAndExpand"
HasUnevenRows="True">
<!-- Need HasUnevenRows = true to enable correct
display in Android-->
<ListView.ItemTemplate>
<DataTemplate >
<ViewCell>
<ContentView Padding="5">
<StackLayout>
<Grid RowSpacing="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Frame OutlineColor="Black"
BackgroundColor="Transparent"
Padding="0"
Grid.Row="4">
<Frame.GestureRecognizers>
<TapGestureRecognizer Tapped="OnPhoneFrameTapped"/>
</Frame.GestureRecognizers>
<StackLayout Orientation="Horizontal"
Padding="10,5,0,5">
<Label Text="Phone:"/>
<Label x:Name="phoneValue"
Text="{Binding Mobile}"/>
</StackLayout>
</Frame> ...
!
After posting the question I sorted out how to get the text I required using the drill down intellisense for stepping down to the required level.
First level is the Frame. Second level is the Stacklayout and the required Label is third level.
I then use Plugin.Messanger to send the email.
Note this also works for using the phone dialer and extracting the phone number to call.
Here is my solution: Note the listChildren[1] is the index of the child label I require.
void OnEmailFrameTapped(object sender, EventArgs args)
{
Frame f = (Frame)sender;
string emailstring = string.Empty;
var fcontent = f.Content;
var myStacklayout = fcontent.GetType();
if (myStacklayout == typeof (StackLayout))
{
StackLayout fStacklayout = (StackLayout)fcontent;
var listChildren = fStacklayout.Children;
var reqLabel = listChildren[1];
var theLabel = reqLabel.GetType();
if (theLabel == typeof(Label))
{
Label emailLabel = (Label)reqLabel;
emailstring = emailLabel.Text;
}
}
var emailMessenger = CrossMessaging.Current.EmailMessenger;
if (emailMessenger.CanSendEmail)
{
// Send simple e-mail to single receiver without attachments, bcc, cc etc.
emailMessenger.SendEmail(emailstring,
"Test Sender",
"Hello from message");
}
}
Using the information that you found, I was also able to narrow the TapGesture to the specific Label I needed which simplified the piece I needed.
Here is the xaml
<Label
Text="{Binding MapUri}"
TextColor="#ab1e2c"
FontAttributes="Bold"
HorizontalTextAlignment="End">
<Label.GestureRecognizers>
<TapGestureRecognizer
Tapped="OnLabelTapped"
NumberOfTapsRequired="1" />
</Label.GestureRecognizers>
</Label>
Here's the .cs
public async void OnLabelTapped(object sender, EventArgs args)
{
string mapUri = string.Empty;
Label l = (Label)sender;
mapUri = l.mapUri;
var result = await Application.Current.MainPage.DisplayAlert("Alert", "Launch Google Maps" , "OK", "Cancel");
if (result)
{
Device.OpenUri(new Uri(mapUri));
}
}