Xamarin forms: Auto scrolling for CarouselPage children - xamarin.forms

I have 4 children (page1,page2,page3, and page4) in a CarouselPage, I need to auto-scroll the children in every 3 seconds. Initially, page1 is showing on the UI, then page2 -> page3 -> page 4 again starting from page1.
I have done like below for this feature using OnCurrentPageChanged() and await Task.Delay(TimeSpan.FromSeconds(3));:
protected async override void OnCurrentPageChanged()
{
base.OnCurrentPageChanged();
await Task.Delay(TimeSpan.FromSeconds(3));
int index = Children.IndexOf(CurrentPage);
if (index == 0)
{
CurrentPage = Children[1];
}
else if (index == 1)
{
CurrentPage = Children[2];
}
else if (index == 2)
{
CurrentPage = Children[3];
}
else if (index == 3)
{
CurrentPage = Children[0];
}
}
The auto-scroll is successful by this approach.
But if I manually scroll the page in between the auto-scroll, then the time delay is reducing. Suddenly(less than 3 sec) the next page is showing on the screen. If I manually swipe the page I need to wait on the page for 3 seconds. How can I solve this issue?

There is a Exciting Library CardsView please look into it.
it will not only solve your problem but your apps looks cool as well.
here is Source Project , https://github.com/AndreiMisiukevich/CardView .
check CarouselSampleXamlView in that, SlideShowDuration property for carousel which handles all stuff.
Hope it helps.

Please do not put the transfer page to the OnCurrentPageChanged method.
You can put it in your Page's constructor.
public partial class MainPage : CarouselPage
{
bool isStart = true;
public MainPage()
{
InitializeComponent();
Device.StartTimer(new TimeSpan(0, 0, 3), () =>
{
Device.BeginInvokeOnMainThread(() =>
{
int index = Children.IndexOf(CurrentPage);
if (index == 0)
{
CurrentPage = Children[1];
isStart = true;
}
else if (index == 1)
{
CurrentPage = Children[2];
isStart = true;
}
else if (index == 2)
{
CurrentPage = Children[3];
isStart = true;
}
else if (index == 3)
{
CurrentPage = Children[0];
isStart = true;
}
});
return isStart; // runs again, or false to stop
});
}
}
If you change the page by swipe, the time delay is not reducing.
However, If you want to wait on the page for 3 seconds after swiping. You have to use custom renderer to achieve it. You should monitor the viewpager's onTouchEventin android(but I cannot found a solution in IOS). This way will have caton, so above way will be better.
[assembly: ExportRenderer(typeof(CarouselPage), typeof(CustomCarouselPageRenderer))]
namespace CarouselPageDemo.Droid
{
public class CustomCarouselPageRenderer: CarouselPageRenderer
{
public CustomCarouselPageRenderer(Context context) : base(context) {
}
protected override void OnElementChanged(ElementChangedEventArgs<CarouselPage> e)
{
base.OnElementChanged(e);
if (this.ChildCount > 0 && this.GetChildAt(0) is ViewPager viewPager)
{
viewPager.Touch -= ViewPagerTouched;
viewPager.Touch += ViewPagerTouched;
}
}
private void ViewPagerTouched(object sender, TouchEventArgs e)
{
MessagingCenter.Send<App, string>(App.Current as App, "OpenPage", "stop");
}
}
}
CarouselPage_CurrentPageChanged method
private async void CarouselPage_CurrentPageChanged(object sender, EventArgs e)
{
var tokenSource = new CancellationTokenSource();
await Task.Delay(TimeSpan.FromSeconds(3), tokenSource.Token);
MessagingCenter.Subscribe<App, string>(App.Current, "OpenPage", (snd, arg) =>
{
tokenSource.Cancel();
});
int index = Children.IndexOf(CurrentPage);
if (index == 0)
{
CurrentPage = Children[1];
}
else if (index == 1)
{
CurrentPage = Children[2];
}
else if (index == 2)
{
CurrentPage = Children[3];
}
else if (index == 3)
{
CurrentPage = Children[0];
}
}

Related

ASP.NET| checkbox's "checked=true" is not setted in the right checkbox

I'm working on a simple multi-staged registration page for a site I'm building, and I give the user the choice of choosing programs/programming languages he knows using checkboxes:
but when I hit the "next" button, in order to go to the next stage, the checkbox I checked isn't set to true, but checkbox no. 18 is set to true(although I didn't check it)
I'm certain it has something to do with the stage before this one, in which I'm building dynamically radio buttons in which the user is choosing his profession (such as Artist, singer and etc').
there are 17 radio buttons, and they are somehow interfering with the next stage, in which the checkbox's checked values are only starting from checkbox no. 18 as I mentioned earlier.
here is some of the code:
else if (int.Parse(ViewState["DivID"].ToString()) == 2)
{
// save the Birthday Date, Language and country of the user.
ViewState["year"] = int.Parse(DropDownYear.SelectedValue);
ViewState["month"] = int.Parse(DropDownMonth.SelectedValue);
ViewState["day"] = int.Parse(DropDownDay.SelectedValue);
ViewState["Language"] = int.Parse(langDropDown.SelectedValue);
ViewState["Country"] = int.Parse(CountryDropDown.SelectedValue);
// ---------------------------------------------
// change from part 2 of the registration to part 3
registrationP2.Visible = false;
BindProfessions(radios, Page);
registrationP3.Visible = true;
radios.Visible = true;
}
else if (int.Parse(ViewState["DivID"].ToString()) == 3)
{
// change from part 3 of the registration to part 4
ViewState["Profid"] = CheckRadio(radios);
registrationP3.Visible = false;
BindKnowledge(CheckboxCon, Page);
registrationP4.Visible = true;
CheckboxCon.Visible = true;
// ---------------------------------------------
//next.Visible = true;
}
else if(int.Parse(ViewState["DivID"].ToString()) == 4)
{
List<int> v = GetCheckBox(CheckboxCon);
ViewState["Knowids"] = GetCheckBox(CheckboxCon);
}
Binding methods:
public static void BindProfessions(HtmlControl ctrl, Page thispage)
{
List<Profession> Plist = Profession.GetProfessionList();
foreach (Profession p in Plist)
{
HtmlInputRadioButton rd_button = new HtmlInputRadioButton();
const string GROUP_NAME = "Professions";
rd_button.Name = GROUP_NAME;
string LinkID = "P" + p.ProfessionID.ToString();
rd_button.Attributes["id"] = LinkID;
RegisterUserControl userprofession = (RegisterUserControl)thispage.LoadControl("~/RegisterUserControl.ascx");
userprofession.imgP = p.ProfPath;
userprofession.fieldName = p.ProfName;
userprofession.IDnum = p.ProfessionID;
userprofession.RadioName = LinkID;
userprofession.EnableViewState = false;
rd_button.EnableViewState = false;
ctrl.Controls.Add(rd_button);
rd_button.Value = p.ProfessionID.ToString();
ctrl.Controls.Add(userprofession);
}
}
public static void BindKnowledge(HtmlControl ctrl, Page thispage)
{
List<Knowledge> Plist = Knowledge.RetKnowledgeList();
foreach (Knowledge p in Plist)
{
HtmlInputCheckBox rd_button = new HtmlInputCheckBox();
const string GROUP_NAME = "knowledge";
rd_button.Name = GROUP_NAME;
string LinkID = "Know" + p.ProgramID.ToString();
rd_button.Attributes["id"] = LinkID;
rd_button.Value = p.ProgramID.ToString();
RegisterUserControl userprofession = (RegisterUserControl)thispage.LoadControl("~/RegisterUserControl.ascx");
userprofession.imgP = p.ProgPath;
userprofession.fieldName = p.PName;
userprofession.IDnum = p.ProgramID;
userprofession.RadioName = LinkID;
userprofession.EnableViewState = false;
rd_button.EnableViewState = false;
ctrl.Controls.Add(rd_button);
ctrl.Controls.Add(userprofession);
}
}
checking methods for both radios and checkbox :
public static int CheckRadio(HtmlControl ctrl)
{
try
{
int counter = 0;
int id = -1;
foreach (Control rdButton in ctrl.Controls)
{
if (rdButton is HtmlInputRadioButton)
{
HtmlInputRadioButton bu = (HtmlInputRadioButton)rdButton;
if (bu.Checked)
{
counter++;
id = int.Parse(bu.Value);
}
}
}
if (counter > 1)
{
return -1;
}
return id;
}
catch (Exception e)
{
return -1;
}
}
public static List<int> GetCheckBox(HtmlControl ctrl)
{
List<int> id_list = new List<int>();
foreach (Control rdButton in ctrl.Controls)
{
if (rdButton is HtmlInputCheckBox)
{
HtmlInputCheckBox bu = (HtmlInputCheckBox)rdButton;
if (bu.Checked)
{
id_list.Add(int.Parse(bu.Value));
}
}
}
return id_list;
}
}
when debugging you can see, that if I choose the first 3 professions, the values returned to me in the List<int> v are 18, 19, and 20
photo: debugging photo
I should mention that after I create the dynamic usercontrols and checkbox/radion buttons, I'm creating them again at postback in protected void Page_Load.
I'm stuck on this for days, and I don't know from where the problem emanates, is it because of ViewState, or the way I'm creating the controls... I really don't know.
Thanks in advance, Idan.
edit:
I played with it a bit, and have found out that when I disable the Binding of the professions which I have initiated earlier in Page_load it does work correctly, page load code look at the second if statement :
protected void Page_Load(object sender, EventArgs e)
{
IsPageRefresh = false;
if (!IsPostBack)
{
ViewState["DivID"] = 1;
ViewState["postids"] = System.Guid.NewGuid().ToString();
Session["postid"] = ViewState["postids"].ToString();
}
else
{
if (ViewState["postids"].ToString() != Session["postid"].ToString())
{
IsPageRefresh = true;
}
Session["postid"] = System.Guid.NewGuid().ToString();
ViewState["postids"] = Session["postid"].ToString();
}
if (int.Parse(ViewState["DivID"].ToString()) == 3)
{
//BindProfessions(radios, Page);
}
else if(int.Parse(ViewState["DivID"].ToString()) == 4)
{
BindKnowledge(CheckboxCon, Page);
}
}
the problem is that I still need to initiate it again after hitting the button in order to get the checked value, how can I fix this thing, and why this is happening? your help would very much be appreciated.
The problem happens because the page recognize that I added 17 new checkbox's, and than when I go over them the first 17 are not checked until the 18'th(the first one of the ones that I checked) what ends up not checking the right checkbox....
And to make it clears I add the other radio buttons to a different div on the page, I don't know what is happening here
for anyone who has the same problem.
I ended up creating the object in Page_PreInit() and it solved the problem, it is recommended(by things I read) to create dynamic objects in Page_PreInit, before anything else is happening to the page.
protected void Page_PreInit(object sender, EventArgs e)
{
try
{
if (!IsPostBack && Session["DivID"] == null)
{
Session["DivID"] = 1;
}
if ((int)Session["DivID"] == 3)
{
InitBindProfessions(Page);
}
else if ((int)Session["DivID"] == 4)
{
InitBindKnowledge(Page);
}
}
catch
{
Response.Redirect("HomePage.aspx");
}
}
InitBindKnowledge and InitBindProfessions are just like BindKnowledge and BindProfession but without adding usercontrols to the control tree

Secondary Toolbar items not fitting the screen

I have a Xamarin.Forms app with FreshMvvm, and I use secondary ToolbarItems. To do it in iOS, I had to make a custom renderer (unlike in Android). I was given a solution on how to implement it here:
ToolbarItems do not look right in iOS
This solution works perfectly for me. But by now, the toolbar menu grew longer, and some of its elements do not fit the iPhone's screen. I can slide the menu and see all the elements, but as soon as I release the screen, the view jumps back up, and while it is held by a finger, the elements are not clickable. How can this be solved? Can the menu made to wrap, or something else?
On Android, the menu stays where I scroll and I can click every item. Can it made stay where scrolled on iOS, too?
Here is my renderer's code:
using CoreGraphics;
using MobileApp.iOS.Renderers;
using MobileApp.iOS.Services;
using MobileApp.Pages;
using MobileApp.Services;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(CustomToolbarContentPage),
typeof(RightToolbarMenuCustomRenderer))]
namespace MobileApp.iOS.Renderers
{
class RightToolbarMenuCustomRenderer : PageRenderer
{
private List<ToolbarItem> _primaryItems;
private List<ToolbarItem> _secondaryItems;
private UITableView _table;
private UITapGestureRecognizer _tapGestureRecognizer;
private UIView _transparentView;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
if (e.NewElement is IAddToolbarItem item)
{
item.ToolbarItemAdded += Item_ToolbarItemAdded;
}
base.OnElementChanged(e);
}
private void Item_ToolbarItemAdded(object sender, System.EventArgs e)
{
if (Element is ContentPage page)
{
_primaryItems = page.ToolbarItems.Where(i => i.Order == ToolbarItemOrder.Primary).ToList();
_secondaryItems = page.ToolbarItems.Where(i => i.Order == ToolbarItemOrder.Secondary).ToList();
_secondaryItems.ForEach(t => page.ToolbarItems.Remove(t));
}
var element = (ContentPage)Element;
if (_secondaryItems?.Count == 0 && element.ToolbarItems.Any(a => (a.IconImageSource as FileImageSource)?.File == "more.png"))
{
element.ToolbarItems.Clear();
}
else if (_secondaryItems?.Count >= 1 && !element.ToolbarItems.Any(a => (a.IconImageSource as FileImageSource)?.File == "more.png"))
{
element.ToolbarItems.Add(new ToolbarItem()
{
Order = ToolbarItemOrder.Primary,
IconImageSource = "more.png",
Priority = 1,
Command = new Command(ToggleDropDownMenuVisibility)
});
}
}
private void ToggleDropDownMenuVisibility()
{
if (!DoesTableExist())
{
if ((View?.Subviews != null)
&& (View.Subviews.Length > 0)
&& (View.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0))
{
_table = OpenDropDownMenu(Element as IAddToolbarItem);
Add(_table);
}
}
else
CloseDropDownMenu();
}
private bool DoesTableExist()
{
if (View?.Subviews != null)
{
foreach (var subview in View.Subviews)
{
if (_table != null && subview == _table)
{
return true;
}
}
}
if (_tapGestureRecognizer != null)
{
_transparentView?.RemoveGestureRecognizer(_tapGestureRecognizer);
_tapGestureRecognizer = null;
}
_table = null;
_tapGestureRecognizer = null;
return false;
}
private UITableView OpenDropDownMenu(IAddToolbarItem secondaryMenuSupport)
{
_transparentView = _transparentView = new UIView(new CGRect(0, 0, View.Bounds.Width, View.Bounds.Height))
{
BackgroundColor = UIColor.FromRGBA(0, 0, 0, 0)
};
_tapGestureRecognizer = new UITapGestureRecognizer(CloseDropDownMenu);
_transparentView.AddGestureRecognizer(_tapGestureRecognizer);
Add(_transparentView);
UITableView table = null;
if (_secondaryItems != null && _secondaryItems.Count > 0)
{
table = new UITableView(GetPositionForDropDownMenu(secondaryMenuSupport.RowHeight, secondaryMenuSupport.TableWidth))
{
Source = new TableSource(_secondaryItems, _transparentView),
ClipsToBounds = false
};
table.ScrollEnabled = true;
table.Layer.ShadowColor = secondaryMenuSupport.ShadowColor.ToCGColor();
table.Layer.ShadowOpacity = secondaryMenuSupport.ShadowOpacity;
table.Layer.ShadowRadius = secondaryMenuSupport.ShadowRadius;
table.Layer.ShadowOffset = new SizeF(secondaryMenuSupport.ShadowOffsetDimension, secondaryMenuSupport.ShadowOffsetDimension);
table.BackgroundColor = secondaryMenuSupport.MenuBackgroundColor.ToUIColor();
}
return table;
}
public override void ViewWillDisappear(bool animated)
{
CloseDropDownMenu();
base.ViewWillDisappear(animated);
}
private RectangleF GetPositionForDropDownMenu(float rowHeight, float tableWidth)
{
if ((View?.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0))
{
return new RectangleF(
(float)View.Bounds.Width - tableWidth,
0,
tableWidth,
_secondaryItems.Count() * rowHeight);
}
else
{
return new RectangleF(0.0f, 0.0f, 0.0f, 0.0f);
}
}
private void CloseDropDownMenu()
{
if (_table != null)
{
if (_tapGestureRecognizer != null)
{
_transparentView?.RemoveGestureRecognizer(_tapGestureRecognizer);
_tapGestureRecognizer = null;
}
if (View?.Subviews != null)
{
foreach (var subview in View.Subviews)
{
if (subview == _table)
{
_table.RemoveFromSuperview();
break;
}
}
if (_transparentView != null)
{
foreach (var subview in View.Subviews)
{
if (subview == _transparentView)
{
_transparentView.RemoveFromSuperview();
break;
}
}
}
}
_table = null;
_transparentView = null;
}
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
if (_table != null)
{
if (Element is IAddToolbarItem secondaryMenuSupport)
PositionExistingDropDownMenu(secondaryMenuSupport.RowHeight, secondaryMenuSupport.TableWidth);
}
}
private void PositionExistingDropDownMenu(float rowHeight, float tableWidth)
{
if ((View?.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0)
&& (_table != null))
{
_table.Frame = GetPositionForDropDownMenu(rowHeight, tableWidth);
}
}
}
}
ADDITION:
public class TableSource : UITableViewSource
{
List<ToolbarItem> _tableItems;
string[] _tableItemTexts;
string CellIdentifier = "TableCell";
UIView _tableSuperView = null;
public TableSource(List<ToolbarItem> items, UIView tableSuperView)
{
_tableItems = items;
_tableSuperView = tableSuperView;
_tableItemTexts = items.Select(a => a.Text).ToArray();
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return _tableItemTexts?.Length ?? 0;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell(CellIdentifier);
string item = _tableItemTexts[indexPath.Row];
if (cell == null)
{ cell = new UITableViewCell(UITableViewCellStyle.Default, CellIdentifier); }
cell.TextLabel.Text = item;
return cell;
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
return 56;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
var command = _tableItems[indexPath.Row].Command;
command.Execute(_tableItems[indexPath.Row].CommandParameter);
tableView.DeselectRow(indexPath, true);
tableView.RemoveFromSuperview();
if (_tableSuperView != null)
{
_tableSuperView.RemoveFromSuperview();
}
}
}
public interface IAddToolbarItem
{
event EventHandler ToolbarItemAdded;
Color CellBackgroundColor { get; }
Color CellTextColor { get; }
Color MenuBackgroundColor { get; }
float RowHeight { get; }
Color ShadowColor { get; }
float ShadowOpacity { get; }
float ShadowRadius { get; }
float ShadowOffsetDimension { get; }
float TableWidth { get; }
}
Here you can download the project that reproduces the issue:
https://github.com/DavidShochet/Public
Well, I still can't reproduce the problem with your updated code. I don't if it is a solution and I just want to clarify my comment here.
In your code, you add the _table to the View:
_table = OpenDropDownMenu(Element as IAddToolbarItem);
Add(_table);
What I want you to have a try is add the _table to _transparentView :
_table = OpenDropDownMenu(Element as IAddToolbarItem);
//Add(_table);
_transparentView.Add(_table);
It would be better if you can provide us a Minimal, Reproducible Example so that I can debug it on my side.
Update:
I found the problem is here, you set the height of table _secondaryItems.Count() * rowHeight) which is longer then the view when the toolbar menu grew longer:
private RectangleF GetPositionForDropDownMenu(float rowHeight, float tableWidth)
{
if ((View?.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0))
{
return new RectangleF(
(float)View.Bounds.Width - tableWidth,
0,
tableWidth,
//here is the cause
_secondaryItems.Count() * rowHeight);
}
else
{
return new RectangleF(0.0f, 0.0f, 0.0f, 0.0f);
}
}
Solution: change the height of tablview to (float)View.Bounds.Height:
private RectangleF GetPositionForDropDownMenu(float rowHeight, float tableWidth)
{
if ((View?.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0))
{
return new RectangleF(
(float)View.Bounds.Width - tableWidth,
0,
tableWidth,
(float)View.Bounds.Height);
}
else
{
return new RectangleF(0.0f, 0.0f, 0.0f, 0.0f);
}
}

Custom Keyboard in Xamarin forms

I've read the many posts on the forum and on StackOverflow and other places on making custom keyboards, but have not found an approach that will work for my Xamarin forms cross-platform project. It is programmatically generated.
For example, I built this keyboard that was recommended in several places:
I try to integrate this into my Xamarin forms app but not able to do this
https://github.com/Vaikesh/CustomKeyboard/blob/master/CustomKeyboard/Activity1.cs
It works fine as a standalone
I want Hebrew language keyboard in my application Like this
I would appreciate any help.
Thank you.
Custom Keyboard in Xamarin forms
You could create a PageRenderer and use native .axml layout file to create the custom Keyboard.
For example, my KeyboardPageRenderer :
[assembly: ExportRenderer(typeof(MyKeyboardPage), typeof(KeyboardPageRenderer))]
...
public class KeyboardPageRenderer : PageRenderer
{
public CustomKeyboardView mKeyboardView;
public EditText mTargetView;
public Android.InputMethodServices.Keyboard mKeyboard;
Activity activity;
global::Android.Views.View view;
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
return;
}
try
{
SetupUserInterface();
SetupEventHandlers();
this.AddView(view);
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine(#" ERROR: ", ex.Message);
}
}
void SetupUserInterface()
{
activity = this.Context as Activity;
view = activity.LayoutInflater.Inflate(Resource.Layout.activity_keyboard, this, false);
mKeyboard = new Android.InputMethodServices.Keyboard(Context, Resource.Xml.keyboard);
mTargetView = view.FindViewById<EditText>(Resource.Id.target);
mKeyboardView = view.FindViewById<CustomKeyboardView>(Resource.Id.keyboard_view);
mKeyboardView.Keyboard = mKeyboard;
}
void SetupEventHandlers()
{
mTargetView.Touch += (sender, e) =>
{
ShowKeyboardWithAnimation();
e.Handled = false;
mTargetView.ShowSoftInputOnFocus = false;
};
mKeyboardView.Key += async (sender, e) =>
{
long eventTime = JavaSystem.CurrentTimeMillis();
KeyEvent ev = new KeyEvent(eventTime, eventTime, KeyEventActions.Down, e.PrimaryCode, 0, 0, 0, 0, KeyEventFlags.SoftKeyboard | KeyEventFlags.KeepTouchMode);
DispatchKeyEvent(ev);
await Task.Delay(1);
mTargetView.RequestFocus();
};
}
public void ShowKeyboardWithAnimation()
{
if (mKeyboardView.Visibility == ViewStates.Gone)
{
mKeyboardView.Visibility = ViewStates.Visible;
Android.Views.Animations.Animation animation = AnimationUtils.LoadAnimation(
Context,
Resource.Animation.slide_in_bottom
);
mKeyboardView.ShowWithAnimation(animation);
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly);
var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly);
view.Measure(msw, msh);
view.Layout(0, 0, r - l, b - t);
}
}
Effect:
.
I wrote up a simple demo about how to implement this feature, you can see it in this GitHub Repository.
I don't know Hebrew, if you need to achieve the effect like the picture you have post, you need custom the layout in keyboard.xml file.
Update :
I am done iOS portion using entry render so only try to do for android portion
I write a EntryRenderer to implement this feature, effect like this, hope this can help you.
public class MyEntry2Renderer : ViewRenderer<MyEntry, TextInputLayout>,
ITextWatcher,
TextView.IOnEditorActionListener
{
private bool _hasFocus;
public CustomKeyboardView mKeyboardView;
public Android.InputMethodServices.Keyboard mKeyboard;
ViewGroup activityRootView;
protected EditText EditText => Control.EditText;
public bool OnEditorAction(TextView v, ImeAction actionId, KeyEvent e)
{
if ((actionId == ImeAction.Done) || ((actionId == ImeAction.ImeNull) && (e.KeyCode == Keycode.Enter)))
{
Control.ClearFocus();
//HideKeyboard();
((IEntryController)Element).SendCompleted();
}
return true;
}
public virtual void AfterTextChanged(IEditable s)
{
}
public virtual void BeforeTextChanged(ICharSequence s, int start, int count, int after)
{
}
public virtual void OnTextChanged(ICharSequence s, int start, int before, int count)
{
if (string.IsNullOrWhiteSpace(Element.Text) && (s.Length() == 0)) return;
((IElementController)Element).SetValueFromRenderer(Entry.TextProperty, s.ToString());
}
protected override TextInputLayout CreateNativeControl()
{
var textInputLayout = new TextInputLayout(Context);
var editText = new EditText(Context);
#region Add the custom Keyboard in your Page
var activity = Forms.Context as Activity;
var rootView = activity.Window.DecorView.FindViewById(Android.Resource.Id.Content);
activity.Window.SetSoftInputMode(SoftInput.StateAlwaysHidden);
activityRootView = ((ViewGroup)rootView).GetChildAt(0) as ViewGroup;
mKeyboardView = new CustomKeyboardView(Forms.Context, null);
Android.Widget.RelativeLayout.LayoutParams layoutParams =
new Android.Widget.RelativeLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.WrapContent); // or wrap_content
layoutParams.AddRule(LayoutRules.AlignParentBottom);
activityRootView.AddView(mKeyboardView, layoutParams);
#endregion
//First open the current page, hide the Keyboard
mKeyboardView.Visibility = ViewStates.Gone;
//Use the custom Keyboard
mKeyboard = new Android.InputMethodServices.Keyboard(Context, Resource.Xml.keyboard2);
mKeyboardView.Keyboard = mKeyboard;
mKeyboardView.Key += async (sender, e) =>
{
long eventTime = JavaSystem.CurrentTimeMillis();
KeyEvent ev = new KeyEvent(eventTime, eventTime, KeyEventActions.Down, e.PrimaryCode, 0, 0, 0, 0, KeyEventFlags.SoftKeyboard | KeyEventFlags.KeepTouchMode);
DispatchKeyEvent(ev);
await Task.Delay(1);
};
textInputLayout.AddView(editText);
return textInputLayout;
}
protected override void OnElementChanged(ElementChangedEventArgs<MyEntry> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
if (Control != null)
EditText.FocusChange -= ControlOnFocusChange;
if (e.NewElement != null)
{
var ctrl = CreateNativeControl();
SetNativeControl(ctrl);
EditText.ShowSoftInputOnFocus = false;
EditText.FocusChange += ControlOnFocusChange;
}
}
private void ControlOnFocusChange(object sender, FocusChangeEventArgs args)
{
_hasFocus = args.HasFocus;
if (_hasFocus)
{
EditText.Post(() =>
{
EditText.RequestFocus();
ShowKeyboardWithAnimation();
});
}
else
{
//Hide the Keyboard
mKeyboardView.Visibility = ViewStates.Gone;
}
}
public void ShowKeyboardWithAnimation()
{
if (mKeyboardView.Visibility == ViewStates.Gone)
{
mKeyboardView.Visibility = ViewStates.Visible;
Android.Views.Animations.Animation animation = AnimationUtils.LoadAnimation(
Context,
Resource.Animation.slide_in_bottom
);
mKeyboardView.ShowWithAnimation(animation);
}
}
}

I'm trying to dispose of an object when the system is low on memory - is there a better way than this?

What I am doing currently is adding an item to the Cache and disposing of my object when that object is removed from the Cache. The logic being that it gets removed when memory consumption gets too high. I'm open to outher suggestions but I would like to avoid creating a thread than continually measures memory statistics if possible. Here is my code:
public class WebServiceCache : ConcurrentDictionary<string, WebServiceCacheObject>, IDisposable
{
private WebServiceCache()
{
if (HttpContext.Current != null && HttpContext.Current.Cache != null)
{
HttpContext.Current.Cache.Add("CacheTest", true, null, DateTime.Now.AddYears(1), System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Low,
(key, obj, reason) => {
if (reason != System.Web.Caching.CacheItemRemovedReason.Removed)
{
WebServiceCache.Current.ClearCache(50);
}
});
}
}
private static WebServiceCache _current;
public static WebServiceCache Current
{
get
{
if (_current != null && _current.IsDisposed)
{
// Might as well clear it fully
_current = null;
}
if (_current == null)
{
_current = new WebServiceCache();
}
return _current;
}
}
public void ClearCache(short percentage)
{
try
{
if (percentage == 100)
{
this.Dispose();
return;
}
var oldest = _current.Min(c => c.Value.LastAccessed);
var newest = _current.Max(c => c.Value.LastAccessed);
var difference = (newest - oldest).TotalSeconds;
var deleteBefore = oldest.AddSeconds((difference / 100) * percentage);
// LINQ doesn't seem to work very well on concurrent dictionaries
//var toDelete = _current.Where(c => DateTime.Compare(c.Value.LastAccessed,deleteBefore) < 0);
var keys = _current.Keys.ToArray();
foreach (var key in keys)
{
if (DateTime.Compare(_current[key].LastAccessed, deleteBefore) < 0)
{
WebServiceCacheObject tmp;
_current.TryRemove(key, out tmp);
tmp = null;
}
}
keys = null;
}
catch
{
// If we throw an exception here then we are probably really low on memory
_current = null;
GC.Collect();
}
}
public bool IsDisposed { get; set; }
public void Dispose()
{
this.Clear();
HttpContext.Current.Cache.Remove("CacheTest");
this.IsDisposed = true;
}
}
In Global.asax
void context_Error(object sender, EventArgs e)
{
Exception ex = _context.Server.GetLastError();
if (ex.InnerException is OutOfMemoryException)
{
if (_NgageWebControls.classes.Caching.WebServiceCache.Current != null)
{
_NgageWebControls.classes.Caching.WebServiceCache.Current.ClearCache(100);
}
}
}
Thanks,
Joe
You can access the ASP.NET Cache from anywhere in your application as the static property:
HttpRuntime.Cache
You don't need to be in the context of a Request (i.e. don't need HttpContext.Current) to do this.
So you should be using it instead of rolling your own caching solution.

Save selected items when using caliburn.micro /Telerik RadGridView /Silverlight

I am using Caliburn micro(1.3)/MVVM and Silverlight. When I update the itemsource RadGridView, I lose the selected items. I found a blog about implementing a behavior to save the selected items when you are implementing MVVM. I can get the selected items, but I cannot set them back once the itemsource is refreshed. Can someoneshow me how to implement this using caliburn.micro and the RadGridVIew? I think the best way to go is to create a caliburn micro convention, but I can only find a reference for creating a convention for selectedItem, not selectedItems.
Can someone show me how to accomplish this? I tried the following, but it does not work.
private static void SetRadGridSelecteditemsConventions()
{
ConventionManager
.AddElementConvention<DataControl>(DataControl.ItemsSourceProperty, "SelectedItem", "SelectionChanged")
.ApplyBinding = (viewModelType, path, property, element, convention) =>
{
ConventionManager.SetBinding(viewModelType, path, property, element, convention, DataControl.ItemsSourceProperty);
if (ConventionManager.HasBinding(element, DataControl.SelectedItemProperty))
return true;
var index = path.LastIndexOf('.');
index = index == -1 ? 0 : index + 1;
var baseName = path.Substring(index);
foreach (var selectionPath in
from potentialName in ConventionManager.DerivePotentialSelectionNames(baseName)
where viewModelType.GetProperty(potentialName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) != null
select path.Replace(baseName, potentialName))
{
var binding = new Binding(selectionPath) { Mode = BindingMode.TwoWay };
BindingOperations.SetBinding(element, DataControl.SelectedItemProperty, binding);
}
return true;
};
}
Thanks,
Stephane
You should use a behavior for this since the SelectedItems property is readonly.
Telerik has an example for this, only the example is not specific for caliburn.micro.
If you add the following class to your project:
public class MultiSelectBehavior : Behavior<RadGridView>
{
public INotifyCollectionChanged SelectedItems
{
get { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(INotifyCollectionChanged), typeof(MultiSelectBehavior), new PropertyMetadata(OnSelectedItemsPropertyChanged));
private static void OnSelectedItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
var collection = args.NewValue as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged += ((MultiSelectBehavior)target).ContextSelectedItems_CollectionChanged;
}
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItems.CollectionChanged += GridSelectedItems_CollectionChanged;
}
void ContextSelectedItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UnsubscribeFromEvents();
Transfer(SelectedItems as IList, AssociatedObject.SelectedItems);
SubscribeToEvents();
}
void GridSelectedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UnsubscribeFromEvents();
Transfer(AssociatedObject.SelectedItems, SelectedItems as IList);
SubscribeToEvents();
}
private void SubscribeToEvents()
{
AssociatedObject.SelectedItems.CollectionChanged += GridSelectedItems_CollectionChanged;
if (SelectedItems != null)
{
SelectedItems.CollectionChanged += ContextSelectedItems_CollectionChanged;
}
}
private void UnsubscribeFromEvents()
{
AssociatedObject.SelectedItems.CollectionChanged -= GridSelectedItems_CollectionChanged;
if (SelectedItems != null)
{
SelectedItems.CollectionChanged -= ContextSelectedItems_CollectionChanged;
}
}
public static void Transfer(IList source, IList target)
{
if (source == null || target == null)
return;
target.Clear();
foreach (var o in source)
{
target.Add(o);
}
}
}
This behavior takes care of the synchronization between collection RadGridView.SelectedItems and MultiSelectBehavior.SelectedItems.
Now we need to have an ObservableCollection in the ViewModel
//Collection holding the selected items
private ObservableCollection<object> selectedGridItems;
public ObservableCollection<object> SelectedGridItems
{
get
{
if (selectedGridItems == null)
selectedGridItems = new ObservableCollection<object>();
return selectedGridItems;
}
set
{
if (selectedGridItems == value) return;
selectedGridItems = value;
NotifyOfPropertyChange(() => SelectedGridItems);
}
}
//Deselect all selected items in the gridview
public void ClearSelectedGridItems()
{
SelectedGridItems.Clear();
}
Last thing is bind the behavior in the view
<telerik:RadGridView x:Name="CustomLogs" AutoGenerateColumns="true" SelectionMode="Extended">
<i:Interaction.Behaviors>
<local:MultiSelectBehavior SelectedItems="{Binding SelectedGridItems}"/>
</i:Interaction.Behaviors>
</telerik:RadGridView>
Thats it, hope it helps you!

Resources