make Xamarin DisplayActionSheet select, display value in Label - xamarin

I have wired up my ContentPage to an instance of a class (g), and this case works fine:
open the page
enter values in the Entry boxes
make a selection from a DisplayActionSheet
click Save
OnSave all the values from the UI are in g, but the value from the DisplayActionSheet is not in the UI where I expect it.
After the DisplayActionSheet thing runs, I want a value for AisleDepthText to display in the UI.
Here is the class that I instantiate into a variable, g
public class GroceryItemForSaving
{
public GroceryItemForSaving() { }
public event PropertyChangedEventHandler PropertyChanged;
private string _AisleDepth;
public string AisleDepth
{
get
{
return _AisleDepth;
}
set
{
_AisleDepth = value;
OnPropertyChanged();
}
}
private string _AisleDepthText;
public string AisleDepthText
{
get
{
return _AisleDepthText;
}
set
{
_AisleDepthText = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I make g the BindingContext like this:
public NewGrocery()
{
InitializeComponent();
BindingContext = g;
}
Here is the relevant XAML.
<Label Text="GroceryName" Grid.Row="0" Grid.Column="1" ></Label>
<Entry Text="{Binding GroceryName}" Grid.Row="0" Grid.Column="2" ></Entry>
<Label Text="Aisle" Grid.Row="1" Grid.Column="1" ></Label>
<Entry Text="{Binding Aisle}" Grid.Row="1" Grid.Column="2"></Entry>
<Label Text="Aisle Depth" Grid.Row="2" Grid.Column="1" ></Label>
<Label Text="{Binding AisleDepthText, Mode=OneWay}" Grid.Row="2" Grid.Column="2" ></Label>
<Button Clicked="ShowAisleDepthChoices" Grid.Row="2" Grid.Column="3" Text="Aisle Depth" ></Button>
The button click handler ShowAisleDepthChoices, makes the ActionSheet display. In the code for that I set the values for AisleDepth and AisleDepthText like this:
public async void ShowAisleDepthChoices(object sender, EventArgs e)
{
var AisleDepth = 0;
var SelectedAisleDepth = await DisplayActionSheet("Aisle Depth", "Cancel", null, "Front", "Middle", "Back", "Back Wall");
switch (SelectedAisleDepth)
{
case "Front":
AisleDepth = 1;
break;
case "Middle":
AisleDepth = 2;
break;
case "Back":
AisleDepth = 3;
break;
case "Back Wall":
AisleDepth = 4;
break;
}
g.AisleDepthText = SelectedAisleDepth;
g.AisleDepth = AisleDepth.ToString();
}
Then after that no value appears in AisleDepthText Label, but when I click Save, the values are in g.AsileDepthText and g.AisleDept exactly where I expect them. NOTE: I can enter a GroceryName directly in the UI and it ends up in g.GroceryName on save.
What do I need to do to make the value for g.AisleDepthText appear in the UI after the DisplayActionSheet does its thing?

GroceryItemForSaving needs to implement INotifyPropertyChanged. You have to code to satisfy the implementation, but you're not declaring that the class uses the interface, so your binding isn't updating like it should.
public class GroceryItemForSaving : INotifyPropertyChanged

Related

how to refer to item in an enumeration within a ListView

I have a class MRU which contains an ObservableCollection of type string. I would like to display the items within the collection on a PopupPage in a ListView.
[DefaultBindingProperty("Items")]
public class MRU<T> :INotifyPropertyChanged
{
public ObservableCollection<T> Items { get; } = new ObservableCollection<T>();
// This is the maximum number of items that will be saved to persistent storage,
// in use the list can grow beyond this number
private int _MaxItems = 5;
public int MaxItems
{
get { return _MaxItems; }
set {
_MaxItems = value;
TrimItems();
OnPropertyChanged();
}
}
public void Add(T t) {
int idx = Items.IndexOf(t);
if (idx > 0)
{
Items.Move(idx, 0);
}
else if (idx == -1)
{
Items.Insert(0, t);
}
}
public T Pop(int i)
{
if (i > 0 && i < Items.Count)
{
Items.Move(i, 0);
}
return Items[0];
}
private void TrimItems()
{
while (Items.Count > _MaxItems)
{
Items.RemoveAt(Items.Count - 1);
}
}
public void Save()
{
App.Current.Properties["MRU"] = SaveToJSON();
App.Current.SavePropertiesAsync();
}
public string SaveToJSON()
{
TrimItems();
string jsonString = JsonSerializer.Serialize(Items);
return jsonString;
}
public int LoadFromJSON(string jsonString)
{
Items.Clear();
ObservableCollection<T> restore = JsonSerializer.Deserialize<ObservableCollection<T>>(jsonString);
foreach (var t in restore)
{
if (Items.Count == _MaxItems) break;
Items.Add(t);
}
return Items.Count;
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
I can display it in my page using the following XAML
<StackLayout
VerticalOptions="Center"
HorizontalOptions="Center"
Padding="0,20">
<Frame BackgroundColor="White" CornerRadius="15">
<StackLayout>
<Label Text="Most Recently Used" FontSize="Large" BackgroundColor="LightBlue" HorizontalOptions="CenterAndExpand" Padding="20,4"/>
<StackLayout >
<ListView ItemsSource="{Binding Items}" SelectionMode="Single">
<!--<ListView.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Vertical">
<Label Text="{Binding XXXXXX}" VerticalOptions="Center" HorizontalTextAlignment="End" FontSize="Medium"/>
</StackLayout>
</DataTemplate>
</ListView.ItemTemplate> -->
</ListView>
</StackLayout>
</StackLayout>
</Frame>
</StackLayout>
Note the commented out ListView.ItemTemplate section. I would like to display the items within an ItemTemplate so that I can add buttons for each object, however I don't know how to declare it . What should XXXXX be? I've tried a zillion unlikely things
In your xaml page, you created a ListView. I found that you forgot to add <ViewCell></ViewCell> tags.
You should use a structure like the following:
<ListView x:Name="listView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If you have an ObservableCollection<string>, to access the string in your binding, you want to do:
Text="{Binding}"
or
Text="{Binding .}"
Both say that you want to bind to whole object. Since the "whole object" in this case is a string, that's what you'll get.

Cannot display ListView data when creating PDF using PdfSharp.Xamarin.Forms

I am new to xamarin and I am using PdfSharp.Xamarin.Forms nuget to create a PDF in Xamarin forms for both Android and iOS. Problem is I cannot render ListView. They have mentioned about it, and need to write a renderer for it. But I have no idea how to create and bind it.
This is how I did it.
<Grid x:Name="mainGrid">
<ScrollView>
<StackLayout Padding="4" Orientation="Vertical">
<!--<Image HeightRequest="80" Source="logojpeg.jpg" Margin="0,0,0,5"/>-->
<Label FontSize="18" TextColor="Black" FontFamily="{StaticResource timesNewRomanBold}" HorizontalOptions="CenterAndExpand" Text="Monthly Motor Renew List of Jayasekara (900585) as at January, 2020"/>
<Label FontSize="18" TextColor="Black" FontFamily="{StaticResource timesNewRomanBold}" HorizontalOptions="CenterAndExpand" Text="Report generated on 27 December, 2019" Margin="0,0,0,5"/>
<ListView x:Name="renewListView"
Footer=""
pdf:PdfRendererAttributes.ListRendererDelegate="{StaticResource PDFSampleListRendererDelegate}"
BackgroundColor="White"
SeparatorVisibility="None"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell IsEnabled="false">
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ScrollView>
</Grid>
In code behind.
public partial class MotorRenewalFinalPrint : ContentPage
{
public MotorRenewalFinalPrint()
{
InitializeComponent();
}
public MotorRenewalFinalPrint (List<MotorRenewalPrintData> newdd)
{
InitializeComponent ();
Title = "Save as PDF";
renewListView.ItemsSource = newdd;
}
private void pdf_Clicked(object sender, EventArgs e)
{
var pdf = PDFManager.GeneratePDFFromView(mainGrid);
var fileManager = DependencyService.Get<IFileIO>();
string filePath = Path.Combine(fileManager.GetMyDocumentsPath(), "formpdf.pdf");
DependencyService.Get<IPdfSave>().Save(pdf, filePath);
DependencyService.Get<IPDFPreviewProvider>().TriggerPreview(filePath);
}
}
Updated...
MainClass
public partial class MainPage : ContentPage
{
private List<Customer> Cus = new List<Customer>();
public MainPage()
{
InitializeComponent();
Customer ss1 = new Customer { Names = "test1", Ages = "10"};
Customer ss2 = new Customer { Names = "test2", Ages = "30" };
Cus.Add(ss1);
Cus.Add(ss2);
//rListView.ItemsSource = Cus;
}
private void Button_Clicked(object sender, System.EventArgs e)
{
var pdf = PDFManager.GeneratePDFFromView(mainGrid);
var fileManager = DependencyService.Get<IFileIO>();
string filePath = Path.Combine(fileManager.GetMyDocumentsPath(), "testpdf.pdf");
DependencyService.Get<IPdfSave>().Save(pdf, filePath);
DependencyService.Get<IPDFPreviewProvider>().TriggerPreview(filePath);
}
}
view
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TestPDF"
xmlns:pdf="clr-namespace:PdfSharp.Xamarin.Forms;assembly=PdfSharp.Xamarin.Forms"
x:Class="TestPDF.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:PDFSampleListRendererDelegate x:Key="PDFSampleListRendererDelegate" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<Grid x:Name="mainGrid">
<ScrollView>
<StackLayout Margin="0,0,0,5">
<Label Text="Welcome to Xamarin.Forms!" HorizontalOptions="CenterAndExpand" TextColor="Black" FontSize="18" VerticalOptions="Center" />
<ListView pdf:PdfRendererAttributes.ListRendererDelegate="{DynamicResource PDFSampleListRendererDelegate}" HeightRequest="150"/>
<Button Text="click" Clicked="Button_Clicked"/>
</StackLayout>
</ScrollView>
</Grid>
</ContentPage.Content>
</ContentPage>
PDFSampleListRendererDelegate
public class PDFSampleListRendererDelegate : PdfListViewRendererDelegate
{
public override void DrawCell(ListView listView, int section, int row, XGraphics page, XRect bounds, double scaleFactor)
{
XFont font = new XFont("times" ?? GlobalFontSettings.FontResolver.DefaultFontName, 15);
var yourObject = (listView.ItemsSource as List<Customer>).ElementAt(row);
page.DrawString(yourObject.Names, font, XBrushes.Black, bounds,
new XStringFormat
{
LineAlignment = XLineAlignment.Center,
Alignment = XStringAlignment.Center,
});
}
public override void DrawFooter(ListView listView, int section, XGraphics page, XRect bounds, double scaleFactor)
{
base.DrawFooter(listView, section, page, bounds, scaleFactor);
}
public override double GetFooterHeight(ListView listView, int section)
{
return base.GetFooterHeight(listView, section);
}
}
you should override DrawCell method
i.e:
public override void DrawCell(ListView listView, int section, int row, XGraphics page, XRect bounds, double scaleFactor)
{
XFont font = new XFont(yourCustomFont ?? GlobalFontSettings.FontResolver.DefaultFontName, label.FontSize * scaleFactor);
var yourObject = (listView.ItemSource as List<YourObjType>).ElementAt(row);
page.DrawString(yourObject.Text, font, XColors.Black, bounds,
new XStringFormat {
LineAlignment = XLineAlignment.Center,
Alignment = XStringAlignment.Center,
});
}

Best approach for show/hide password toggle functionality in Xamarin traditional approach

We are working on show/ hide password toggle functionality in Xamarin traditional approach. What is the best place to implement it? Is it in Xamarin.iOS &. Droid or in Xamarin.Core?
If it is in Xamarin.Core, can you let us know the process. Is it by value convertors?
Thanks in advance.
Recently, Microsoft MVP Charlin, wrote an article showing how to do this using Event Triggers in the Xamarin Forms code:
She was able to do it simply using a new ShowPasswordTriggerAction of type TriggerAction that implemented INotifyPropertyChanged.
Therein, she created a HidePassword bool property that Invoke a PropertyChanged event which changes the Source of the Icon image:
protected override void Invoke(ImageButton sender)
{
sender.Source = HidePassword ? ShowIcon : HideIcon;
HidePassword = !HidePassword;
}
Then place the Entry and ImageButton inside a layout (like a Frame or horizontally oriented LinearLayout) as shown:
<Entry Placeholder="Password"
IsPassword="{Binding Source={x:Reference ShowPasswordActualTrigger}, Path=HidePassword}"/>
<ImageButton VerticalOptions="Center"
HeightRequest="20"
HorizontalOptions="End"
Source="ic_eye_hide">
<ImageButton.Triggers>
<EventTrigger Event="Clicked">
<local:ShowPasswordTriggerAction ShowIcon="ic_eye"
HideIcon="ic_eye_hide"
x:Name="ShowPasswordActualTrigger"/>
</EventTrigger>
</ImageButton.Triggers>
</ImageButton>
We always use custom controls to show/hide password while entering the password using effects.
Android:
Create the control manually in ‘OnDrawableTouchListener’ method where, we are adding the ShowPass and HidePass icons to the entry control, changing them on the basis of user touch action and attaching it on effect invocation which will be fired when the effect is added to the control.
public class OnDrawableTouchListener : Java.Lang.Object, Android.Views.View.IOnTouchListener
{
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
if (v is EditText && e.Action == MotionEventActions.Up)
{
EditText editText = (EditText)v;
if (e.RawX >= (editText.Right - editText.GetCompoundDrawables()[2].Bounds.Width()))
{
if (editText.TransformationMethod == null)
{
editText.TransformationMethod = PasswordTransformationMethod.Instance;
editText.SetCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, Resource.Drawable.ShowPass, 0);
}
else
{
editText.TransformationMethod = null;
editText.SetCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, Resource.Drawable.HidePass, 0);
}
return true;
}
}
return false;
}
}
Result:
IOS:
Create the control manually in 'ConfigureControl' method where we are adding the ShowPass and HidePassicons to the entry control, changing them on the basis of user touch action; and attaching it on effect invocation which will be fired when the effect will be added to the control.
private void ConfigureControl()
{
if (Control != null)
{
UITextField vUpdatedEntry = (UITextField)Control;
var buttonRect = UIButton.FromType(UIButtonType.Custom);
buttonRect.SetImage(new UIImage("ShowPass"), UIControlState.Normal);
buttonRect.TouchUpInside += (object sender, EventArgs e1) =>
{
if (vUpdatedEntry.SecureTextEntry)
{
vUpdatedEntry.SecureTextEntry = false;
buttonRect.SetImage(new UIImage("HidePass"), UIControlState.Normal);
}
else
{
vUpdatedEntry.SecureTextEntry = true;
buttonRect.SetImage(new UIImage("ShowPass"), UIControlState.Normal);
}
};
vUpdatedEntry.ShouldChangeCharacters += (textField, range, replacementString) =>
{
string text = vUpdatedEntry.Text;
var result = text.Substring(0, (int)range.Location) + replacementString + text.Substring((int)range.Location + (int)range.Length);
vUpdatedEntry.Text = result;
return false;
};
buttonRect.Frame = new CoreGraphics.CGRect(10.0f, 0.0f, 15.0f, 15.0f);
buttonRect.ContentMode = UIViewContentMode.Right;
UIView paddingViewRight = new UIView(new System.Drawing.RectangleF(5.0f, -5.0f, 30.0f, 18.0f));
paddingViewRight.Add(buttonRect);
paddingViewRight.ContentMode = UIViewContentMode.BottomRight;
vUpdatedEntry.LeftView = paddingViewRight;
vUpdatedEntry.LeftViewMode = UITextFieldViewMode.Always;
Control.Layer.CornerRadius = 4;
Control.Layer.BorderColor = new CoreGraphics.CGColor(255, 255, 255);
Control.Layer.MasksToBounds = true;
vUpdatedEntry.TextAlignment = UITextAlignment.Left;
}
}
Result:
For more details, please refer to the article below.
https://www.c-sharpcorner.com/article/xamarin-forms-tip-implement-show-hide-password-using-effects/
You could download the source file from GitHub for reference.
https://github.com/techierathore/ShowHidePassEx.git
You can use the PhantomLib library to do this. It has a control which allows you to have a show/hide icon for the password with examples. Just install the nuget. https://github.com/OSTUSA/PhantomLib
Your UI codes like this having a entry and image button
source to named accroding to your ui
<Frame CornerRadius="30" Background="white" Padding="0" HeightRequest="43" Margin="0,17,0,0">
<StackLayout Orientation="Horizontal">
<Entry x:Name="eLoginPassword"
Margin="15,-10,0,-15"
HorizontalOptions="FillAndExpand"
IsPassword="True"
Placeholder="Password"/>
<ImageButton
x:Name="ibToggleLoginPass"
Clicked="IbToggleLoginPass"
Source="eyeclosed"
Margin="0,0,13,0"
BackgroundColor="White"
HorizontalOptions="End"
/>
</StackLayout>
</Frame>
in C# code
// IbToggleLoginPass your defined method in xaml
//"eye" is drawable name for open eye and "eyeclosed" is drawable name for closed eye
private void IbToggleLoginPass(object sender, EventArgs e)
{
bool isPass = eLoginPassword.IsPassword;
ibToggleLoginPa`enter code here`ss.Source = isPass ? "eye" : "eyeclosed";
eLoginPassword.IsPassword = !isPass;
}
Trigger and a command
The trigger changes the icon, and the command changes the entry.
View xaml
<Grid>
<Entry Placeholder="Password" Text="{Binding Password, Mode=TwoWay}" IsPassword="{Binding IsPassword}" />
<ImageButton BackgroundColor="Transparent" WidthRequest="24" VerticalOptions="Center" TranslationY="-5" TranslationX="-10" HorizontalOptions="End"
Command="{Binding ToggleIsPassword}"
Source="eye" >
<ImageButton.Triggers>
<DataTrigger TargetType="ImageButton" Binding="{Binding IsPassword}" Value="True" >
<Setter Property="Source" Value="eye-slash" />
</DataTrigger>
</ImageButton.Triggers>
</ImageButton>
</Grid>
And in my ViewModel
private bool _IsPassword = true;
public bool IsPassword
{
get
{
return _IsPassword;
}
set
{
_IsPassword = value;
RaisePropertyChanged(() => IsPassword);
}
}
public ICommand ToggleIsPassword => new Command(() => IsPassword = !IsPassword);

Multi-select Listview

This is my list view it should contain the list of activities came from my SQLite database:
<ListView SeparatorVisibility="None" x:Name="lstActivity" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame StyleClass="lstframe" CornerRadius="0" BorderColor="Transparent" HasShadow="False">
<StackLayout StyleClass="lstContainer" VerticalOptions="CenterAndExpand">
<Grid>
<Label StyleClass="lstActivityName" Grid.Row="0" Grid.Column="0" Text="{Binding ActivityDescription}">
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String">
<On Platform="Android" Value="Poppins-Regular.otf#Poppins-Regular"/>
</OnPlatform>
</Label.FontFamily>
</Label>
<Switch Grid.Row="0" Grid.Column="1" IsToggled="{Binding Selected}" />
</Grid>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here is how I populate the list view this will return atleast five(5) activities:
public void Get_Activities()
{
try
{
var db = DependencyService.Get<ISQLiteDB>();
var conn = db.GetConnection();
var getActivity = conn.QueryAsync<ActivityTable>("SELECT * FROM tblActivity WHERE Deleted != '1' ORDER BY ActivityDescription");
var resultCount = getActivity.Result.Count;
if (resultCount > 0)
{
var result = getActivity.Result;
lstActivity.ItemsSource = result;
lstActivity.IsVisible = true;
}
else
{
lstActivity.IsVisible = false;
}
}
catch (Exception ex)
{
//Crashes.TrackError(ex);
}
}
Selected item binding:
public class SelectData
{
public bool Selected { get; set; }
}
Get selected Items on click:
private void BtnClose_Clicked(object sender, EventArgs e)
{
foreach (var x in result)
{
if (x.Selected)
{
// do something with the selected items
}
}
}
I posted another question regarding on multi-select list view my problem is I don't know how to proceed when I use the answers given to me. How can I get the the selected values because I will save the selected values to my database?
your Switch is bound to the Selected property of your model. Just iterate (or use LINQ) to get the items that are selected.
// you need to maintain a reference to result
foreach(var x in result)
{
if (x.Selected)
{
// do something with the selected items
}
}
LINQ
var selected = result.Where(x => x.Selected).ToList();
you will also need to have a class level reference to result
// you will need to change this to reflect the actual type of result
List<MyClass> result;
public void Get_Activities()
{
...
result = getActivity.Result;
...
For multi-select Listview, I have wrote a working example in my blog. Hope this helps : https://androidwithashray.blogspot.com/2018/03/multiselect-list-view-using-xamarin.html?view=flipcard

How can I stop and start a timed progress bar in Xamarin?

There is a question with an answer that shows how a progress bar can be created that runs for a specified period of time. Here's a link to that question:
How can I create a bar area that slowly fills from left to right over 5, 10 or ?? seconds?
I have tested this out and it works well. However I would like to find out how I can extend this so that the progress bar can be cancelled / stopped before completed and then restarted again.
The question and answer were very popular so it seems like this is something that might benefit many people.
I would appreciate any ideas and feedback on possible ways this could be done.
Update 1:
I tried to implement the solution but I am getting an error and would appreciate some advice. I'm using all your new code and I change from the old to the new here:
<local:TimerView x:Name="timerView">
<local:TimerView.ProgressBar>
<BoxView BackgroundColor="Maroon" />
</local:TimerView.ProgressBar>
<local:TimerView.TrackBar>
<BoxView BackgroundColor="Gray" />
</local:TimerView.TrackBar>
</local:TimerView>
<!--<Grid x:Name="a">
<local:TimerView x:Name="timerView1" VerticalOptions="FillAndExpand">
<local:TimerView.ProgressBar>
<Frame HasShadow="false" Padding="0" Margin="0" BackgroundColor="#AAAAAA" CornerRadius="0" VerticalOptions="FillAndExpand" />
</local:TimerView.ProgressBar>
<local:TimerView.TrackBar>
<Frame HasShadow="false" Padding="0" Margin="0" CornerRadius="0" BackgroundColor="#EEEEEE" VerticalOptions="FillAndExpand" />
</local:TimerView.TrackBar>
</local:TimerView>
</Grid>
<Grid x:Name="b">
<local:TimerView x:Name="timerView2" VerticalOptions="FillAndExpand">
<local:TimerView.ProgressBar>
<Frame HasShadow="false" Padding="0" Margin="0" BackgroundColor="#AAAAAA" CornerRadius="0" VerticalOptions="FillAndExpand" />
</local:TimerView.ProgressBar>
<local:TimerView.TrackBar>
<Frame HasShadow="false" Padding="0" Margin="0" CornerRadius="0" BackgroundColor="#EEEEEE" VerticalOptions="FillAndExpand" />
</local:TimerView.TrackBar>
</local:TimerView>
</Grid>-->
Three questions
First - I noticed you split timerView into two files. The properties file appears to be in some way linked to the main file. Graphically the properties file appears indented from timerView. How do you do this linking in Visual Studio? I just created two files, does that make a difference.
Second - When I try to compile the code I am getting this error:
/Users//Documents/Phone app/Japanese7/Japanese/Views/Phrases/PhrasesFrame.xaml(10,10): Error: Position 117:10. Missing a public static GetProgressBar or a public instance property getter for the attached property "Japanese.TimerView.ProgressBarProperty" (Japanese)
Do you have any ideas what might be causing this? Everything looks the same as before.
Third - I notice you use BoxView and I used a Frame. Would the code work with either?
Update 2:
In my backend C# code I use the following to start the timer:
timerView.StartTimerCommand
.Execute(TimeSpan.FromSeconds(App.pti.Val()));
I tried to stop the timer with some similar syntax but there's some problem. Can you let me know how I can go about stopping the timer when it's used with C# back-end rather than the MVVM in your solution:
timerView.StopTimerCommand.Execute(); // Give syntax error
Step 1: Add cancel method to ViewExtensions:
public static class ViewExtensions
{
static string WIDTH_ANIMATION_NAME = "WidthTo";
public static Task<bool> WidthTo(this VisualElement self, double toWidth, uint length = 250, Easing easing = null)
{
...
}
public static void CancelWidthToAnimation(this VisualElement self)
{
if(self.AnimationIsRunning(WIDTH_ANIMATION_NAME))
self.AbortAnimation(WIDTH_ANIMATION_NAME);
}
}
Step 2: Add bindable properties for 'pause' and 'stop'/'cancel' commands; and a property to track whether timer is running.
public static readonly BindableProperty PauseTimerCommandProperty =
BindableProperty.Create(
"PauseTimerCommand", typeof(ICommand), typeof(TimerView),
defaultBindingMode: BindingMode.OneWayToSource,
defaultValue: default(ICommand));
public ICommand PauseTimerCommand
{
get { return (ICommand)GetValue(PauseTimerCommandProperty); }
set { SetValue(PauseTimerCommandProperty, value); }
}
public static readonly BindableProperty StopTimerCommandProperty =
BindableProperty.Create(
"StopTimerCommand", typeof(ICommand), typeof(TimerView),
defaultBindingMode: BindingMode.OneWayToSource,
defaultValue: default(ICommand));
public ICommand StopTimerCommand
{
get { return (ICommand)GetValue(StopTimerCommandProperty); }
set { SetValue(StopTimerCommandProperty, value); }
}
public static readonly BindableProperty IsTimerRunningProperty =
BindableProperty.Create(
"IsTimerRunning", typeof(bool), typeof(TimerView),
defaultBindingMode: BindingMode.OneWayToSource,
defaultValue: default(bool), propertyChanged: OnIsTimerRunningChanged);
public bool IsTimerRunning
{
get { return (bool)GetValue(IsTimerRunningProperty); }
set { SetValue(IsTimerRunningProperty, value); }
}
private static void OnIsTimerRunningChanged(BindableObject bindable, object oldValue, object newValue)
{
((TimerView)bindable).OnIsTimerRunningChangedImpl((bool)oldValue, (bool)newValue);
}
Step 3: Update TimerView as below to use a StopWatch to track time, pause, and cancel.
public partial class TimerView : AbsoluteLayout
{
readonly Stopwatch _stopWatch = new Stopwatch();
public TimerView()
{
...
}
async void HandleStartTimerCommand(object param = null)
{
if (IsTimerRunning)
return;
ParseForTime(param);
if (InitRemainingTime())
_stopWatch.Reset();
SetProgressBarWidth();
IsTimerRunning = true;
//Start animation
await ProgressBar.WidthTo(0, Convert.ToUInt32(RemainingTime.TotalMilliseconds));
//reset state
IsTimerRunning = false;
}
void HandlePauseTimerCommand(object unused)
{
if (!IsTimerRunning)
return;
ProgressBar.CancelWidthToAnimation(); //abort animation
}
void HandleStopTimerCommand(object unused)
{
if (!IsTimerRunning)
return;
ProgressBar.CancelWidthToAnimation(); //abort animation
ResetTimer(); //and reset timer
}
protected virtual void OnIsTimerRunningChangedImpl(bool oldValue, bool newValue)
{
if (IsTimerRunning)
{
_stopWatch.Start();
StartIntervalTimer(); //to update RemainingTime
}
else
_stopWatch.Stop();
((Command)StartTimerCommand).ChangeCanExecute();
((Command)PauseTimerCommand).ChangeCanExecute();
((Command)StopTimerCommand).ChangeCanExecute();
}
bool _intervalTimer;
void StartIntervalTimer()
{
if (_intervalTimer)
return;
Device.StartTimer(TimeSpan.FromMilliseconds(100), () =>
{
if(IsTimerRunning)
{
var remainingTime = Time.TotalMilliseconds - _stopWatch.Elapsed.TotalMilliseconds;
if (remainingTime <= 100)
{
_intervalTimer = false;
ResetTimer();
}
else
RemainingTime = TimeSpan.FromMilliseconds(remainingTime);
}
return _intervalTimer = IsTimerRunning; //stop device-timer if timer was stopped
});
}
private void ResetTimer()
{
ProgressBar.CancelWidthToAnimation();
RemainingTime = default(TimeSpan); //reset timer
SetProgressBarWidth(); //reset width
}
void SetProgressBarWidth()
{
if (RemainingTime == Time)
SetLayoutBounds(ProgressBar, new Rectangle(0, 0, Width, Height));
else
{
var progress = ((double)RemainingTime.Seconds / Time.Seconds);
SetLayoutBounds(ProgressBar, new Rectangle(0, 0, Width * progress, Height));
}
}
...
}
Sample Usage
<controls:TimerView x:Name="timerView">
<controls:TimerView.ProgressBar>
<BoxView BackgroundColor="Maroon" />
</controls:TimerView.ProgressBar>
<controls:TimerView.TrackBar>
<BoxView BackgroundColor="Gray" />
</controls:TimerView.TrackBar>
</controls:TimerView>
<Label Text="{Binding Path=RemainingTime, StringFormat='{0:%s}:{0:%f}', Source={x:Reference timerView}}" />
<Button Command="{Binding StartTimerCommand, Source={x:Reference timerView}}" Text="Start Timer">
<Button.CommandParameter>
<x:TimeSpan>0:0:20</x:TimeSpan>
</Button.CommandParameter>
</Button>
<Button Command="{Binding PauseTimerCommand, Source={x:Reference timerView}}" Text="Pause Timer" />
<Button Command="{Binding StopTimerCommand, Source={x:Reference timerView}}" Text="Stop Timer" />
Working sample uploaded at TimerBarSample
EDIT 1
First - It really doesn't make a difference - you can even merge all code into one file. Indented linking can be achieved using <DependentOn /> tag - similar to what is used for code-behind cs for XAML files.
Second - I had added protected access-modifiers to bindable properties' getters or setters. But looks like it fails when XAMLC is applied. I have updated the code in the github sample.
Third - Yes, any control that inherits from View (be it be BoxView or Frame) can be used.
EDIT 2
As these commands (bindable properties) are of type ICommand, in order to Execute - you need to pass in a parameter. In case the command doesn't need a parameter - you can use null.
Recommended usage:
if(timerView.StopTimerCommand.CanExecute(null))
timerView.StopTimerCommand.Execute(null);

Resources