I am developing a complex App and made use of the advantages of Xamarin.Forms.Shell for layout and navigation. Now there are a few annoying things that I haven't been able to find a solution for yet.
The application itself and its content is German. Is there a clean way to change the hard-coded "more" tab-text to its German translation ("Mehr")?
Eventhough the More-tab is quite nice it does not fit my needs for the Layout of the App. When pressing it I want to show a view containing more than just a list of Icons with Labels. I want to divide the view into sections with captions and inside these Captions I want to show the coherent pages of the section. The easiest way would be to replace the multipage-"More-Tab" by a singlepage-tab and just design the page as described, but I like the Menu-Appearance of the More-tab and I want to keep that, if possible.
Thanks in Advance for any help or advice.
You could custom ShellRenderer to modify the Text of More tab.
The CustomShellRenderer.cs in Android solution:
public class CustomShellRenderer : ShellRenderer
{
public CustomShellRenderer(Context context) : base(context)
{
}
protected override IShellBottomNavViewAppearanceTracker CreateBottomNavViewAppearanceTracker(ShellItem shellItem)
{
return new MarginedTabBarAppearance();
}
}
public class MarginedTabBarAppearance : IShellBottomNavViewAppearanceTracker
{
public void Dispose()
{
}
public void ResetAppearance(BottomNavigationView bottomView)
{
}
public void SetAppearance(BottomNavigationView bottomView, ShellAppearance appearance)
{
}
public void SetAppearance(BottomNavigationView bottomView, IShellAppearanceElement appearance)
{
if(null != bottomView.Menu.GetItem(4))
{
IMenuItem menuItem = bottomView.Menu.GetItem(4);
menuItem.SetTitle(Resource.String.More);
}
}
}
The strings.xml:
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<string name="More">Mehr</string>
</resources>
The effect:
The CustomShellRenderer.cs in iOS solution:
public class CustomShellRenderer: ShellRenderer
{
protected override IShellTabBarAppearanceTracker CreateTabBarAppearanceTracker()
{
return new TabBarAppearance();
}
}
public class TabBarAppearance : IShellTabBarAppearanceTracker
{
public void Dispose()
{
}
public void ResetAppearance(UITabBarController controller)
{
}
public void SetAppearance(UITabBarController controller, ShellAppearance appearance)
{
}
public void UpdateLayout(UITabBarController controller)
{
UITabBar tb = controller.MoreNavigationController.TabBarController.TabBar;
if (tb.Subviews.Length > 4)
{
UIView tbb = tb.Subviews[4];
UILabel label = (UILabel)tbb.Subviews[1];
label.Text = "Mehr";
}
}
}
The effect:
The above code based on this official sample project(Xamarin.Forms - Xaminals).
Related
i have a method that need to use native droid functions. I am using Dependency services to achieve that which is fine, however i also need to send a value that gets filled in my standard project.When debugging i see the value in the standard however once i enter droid the value is null i have also tried to make the list Static but not help
My Service
public interface INavigationService
{
void PushDictionary(List<Word> allWordsOfUserForAutomat);
}
My Implementation
public class NavigationImplementation : Activities.INavigationService
{
public void PushDictionary(List<Word> allWordsOfUserForAutomat) //HERE I SEE THE VALUE
{
Intent intent = new Intent(MainActivity.Instance,typeof(LockScreenDictionary));
MainActivity.Instance.StartActivity(intent);
}
}
My standard
protected void LockScreen()
{
if (!viewDisabled)
{
DependencyService.Get<INavigationService>().PushDictionary(_allWordsOfUserForAutomat); //HERE I SEE THE VALUE
}
else
{
NotificationService.ShowToast("Nothing to play");
}
}
My droid project
[Activity(Label = "LockScreenDictionary", Theme = "#style/Theme.Splash")]
public class LockScreenDictionary : FormsAppCompatActivity
{
private List<Word> _allWordsOfUserForAutomat; //HERE ITS NULL
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
LangUpDictionaryPlayer.PlayAutomat(_allWordsOfUserForAutomat); //HERE ITS NULL
}
}
You should pass the allWordsOfUserForAutomat to Intent:
In your Implementation:
public class NavigationImplementation : INavigationService
{
public void PushDictionary(List<Word> allWordsOfUserForAutomat) //HERE I SEE THE VALUE
{
Intent intent = new Intent(MainActivity.Instance, typeof(LockScreenDictionary));
//pass data
intent.PutExtra("myData", allWordsOfUserForAutomat);
MainActivity.Instance.StartActivity(intent);
}
}
In your droid project:
public class LockScreenDictionary : FormsAppCompatActivity
{
private List<Word> _allWordsOfUserForAutomat; //HERE ITS NULL
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
_allWordsOfUserForAutomat = Intent.Extras.GetInt("myData");
LangUpDictionaryPlayer.PlayAutomat(_allWordsOfUserForAutomat); //HERE ITS NULL
}
}
I am having an issue that relates to a post on GitHub. User nirbil summarizes it best:
If a website utilizes the historyApi then the WebView's CanGoBack CanGoForward are wrong.
Steps to reproduce -
Set the webview's source to a single page web application
Navigate within the application to a different url (should utilize
the pushstate)
Navigate back -> CanGoForward changes to true
Navigate within the application again by following some link ->
CanGoForward erroneously does not change to false
On windows this behavior can be traced to the webview's renderer - https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.UAP/WebViewRenderer.cs
Here is the original post on GitHub https://github.com/xamarin/Xamarin.Forms/issues/7691
A lot of ideas are suggested on how to solve the problem however, my grasp of Xamarin Forms is somewhat lacking. Any ideas on how to solve the issue?
You could add the source of webview to use renderer. I use the code sample on GitHub. https://github.com/xamarin/Xamarin.Forms/issues/7691
The original:
Set the source of webview.
<WebView x:Name="webView" Source="https://www.google.com" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"></WebView>
Now:
This question on GitHub has been collected. You could follow it on GitHub. It would be fixed soon.
I solved it using a custom Xamarin Forms WebView with custom CanGoBack/Forward properties.
You can find the it here:
https://github.com/nirbil/XF.CanGoWebView
Following are the main bits of the solution.
CustomWebView control with new bindable properties:
public class CustomWebView : WebView
{
public CustomWebView() {}
public static BindableProperty CustomCanGoForwardProperty =
BindableProperty.Create(
nameof(CustomCanGoForward),
typeof(bool),
typeof(CustomWebView),
false,
BindingMode.OneWayToSource);
public static BindableProperty CustomCanGoBackProperty =
BindableProperty.Create(
nameof(CustomCanGoBack),
typeof(bool),
typeof(CustomWebView),
defaultValue: false,
BindingMode.OneWayToSource);
public bool CustomCanGoForward
{
get => (bool)GetValue(CustomCanGoForwardProperty);
set => SetValue(CustomCanGoForwardProperty, value);
}
public bool CustomCanGoBack
{
get => (bool)GetValue(CustomCanGoBackProperty);
set => SetValue(CustomCanGoBackProperty, value);
}
}
Custom WebViewRenderer on Android:
public class CustomWebViewRenderer : WebViewRenderer
{
protected override WebViewClient GetWebViewClient()
{
CustomWebViewClient webViewClient = new CustomWebViewClient(this);
webViewClient.AddressChanged += AddressChanged;
return webViewClient;
}
private void AddressChanged(string url)
{
if (Element is CustomWebView customWebView && Control != null)
{
customWebView.CustomCanGoBack = Control.CanGoBack();
customWebView.CustomCanGoForward = Control.CanGoForward();
}
}
}
Android CustomWebViewClient that intercepts changes and alerts the renderer:
public class CustomWebViewClient: FormsWebViewClient
{
public delegate void AddressChangedEventHandler(string url);
public event AddressChangedEventHandler AddressChanged;
public CustomWebViewClient(WebViewRenderer renderer) : base(renderer) {}
public override void DoUpdateVisitedHistory(WebView view, string url, bool isReload)
{
base.DoUpdateVisitedHistory(view, url, isReload);
AddressChanged?.Invoke(view.Url);
}
}
Custom WebViewRenderer on UWP:
public class CustomWebViewRenderer : WebViewRenderer
{
bool _registeredControl = false;
long _goBackRegistrationToken = 0;
long _goForwardRegistrationToken = 0;
protected override void Dispose(bool disposing)
{
UnregisterControl();
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
RegisterControl();
}
private void UnregisterControl()
{
if (Control != null && _registeredControl)
{
_registeredControl = false;
Control.UnregisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoBackProperty, _goBackRegistrationToken);
Control.UnregisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoForwardProperty, _goForwardRegistrationToken);
}
}
private void RegisterControl()
{
if (Control != null && !_registeredControl)
{
_registeredControl = true;
_goBackRegistrationToken = Control.RegisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoBackProperty, new Windows.UI.Xaml.DependencyPropertyChangedCallback(OnWebViewCanGoBackChanged));
_goForwardRegistrationToken = Control.RegisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoForwardProperty, new Windows.UI.Xaml.DependencyPropertyChangedCallback(OnWebViewCanGoForwardChanged));
}
}
private void OnWebViewCanGoBackChanged(Windows.UI.Xaml.DependencyObject sender, Windows.UI.Xaml.DependencyProperty dp)
{
((CustomWebView)Element).CustomCanGoBack = Control.CanGoBack;
}
private void OnWebViewCanGoForwardChanged(Windows.UI.Xaml.DependencyObject sender, Windows.UI.Xaml.DependencyProperty dp)
{
((CustomWebView)Element).CustomCanGoForward = Control.CanGoForward;
}
}
In my code, MyTab extendes Tab and until version 8 existed the method setEventHandler (...);
As I am now using the javaFX 12 version, this method is private and I can not use it anymore.
I also do not have access to the variable eventHandlerManager of Tab.
How can I access this functionality in JavaFX 12?
Here is an example of the code.
public class MyTab extends Tab {
...
protected ObjectProperty<EventHandler<EventAction>> onEventDockRequest=null;
public void setOnEventDockRequest(EventHandler<EventAction> value) {
onEventDockRequestProperty().set(value);
}
public final ObjectProperty<EventHandler<EventAction>> onEventDockRequestProperty() {
if (onEventDockRequest == null) {
onEventDockRequest = new ObjectPropertyBase<EventHandler<EventAction>>() {
#Override protected void invalidated() {
setEventHandler(EventAction.DOCK_REQUEST, get()); // here error
}
#Override public Object getBean() {
return DTab.this;
}
#Override public String getName() {
return "onEventDockRequest";
}
};
}
return onEventDockRequest;
}
}
I am working in new Scout Neon and I am starting to get error :
Assertion error: Property 'modal' cannot be changed because Form is already showing
My forms has properties :
#Override
protected int getConfiguredModalityHint() {
return MODALITY_HINT_MODELESS;
}
#Override
protected int getConfiguredDisplayHint() {
return DISPLAY_HINT_VIEW;
}
#Override
protected String getConfiguredDisplayViewId() {
return VIEW_ID_CENTER;
}
What I did wrong?
EDIT : Marko
I add page in MyOutline witch extends from AbstractOutline
public class MyOutline extends AbstractOutline {
#Override
protected String getConfiguredTitle() {
return TEXTS.get("MyOutline");
}
#Override
protected void execCreateChildPages(final List<IPage<?>> pageList) {
final MyPage myPage = new MyPage();
pageList.add(myPage);
super.execCreateChildPages(pageList);
}
}
MyPage is only a wrapper page for form.
public class MyPage extends AbstractPageWithNodes {
#Override
protected boolean getConfiguredLeaf() {
return true;
}
#Override
protected boolean getConfiguredTableVisible() {
return false;
}
#Override
protected String getConfiguredTitle() {
return TEXTS.get("MyPage");
}
#Override
protected Class<? extends IForm> getConfiguredDetailForm() {
return MyForm.class;
}
}
and my form is nothing spacial :
#FormData(value = MyFormData.class, sdkCommand = FormData.SdkCommand.CREATE)
public class MyForm extends AbstractForm {
/**
* Method start Form for adding new person.
*/
public void startNew() {
this.startInternal(new NewHandler());
}
#Override
protected boolean getConfiguredAskIfNeedSave() {
return false;
}
#Override
protected int getConfiguredModalityHint() {
return MODALITY_HINT_MODELESS;
}
#Override
protected int getConfiguredDisplayHint() {
return DISPLAY_HINT_VIEW;
}
#Override
protected String getConfiguredDisplayViewId() {
return VIEW_ID_CENTER;
}
public MainBox getMainBox() {
...
But when I want to open this Page (on start application is not opened on this page), (and I do not do anything else before) I get error.
2016-01-22 11:13:56,236 ERROR scout-model-thread-11 'Processing JSON request' o.e.scout.rt.platform.exception.ExceptionHandler -
org.eclipse.scout.rt.platform.util.Assertions$AssertionException: Assertion error: Property 'modal' cannot be changed because Form is already showing
at org.eclipse.scout.rt.platform.util.Assertions.fail(Assertions.java:581) ~[org.eclipse.scout.rt.platform-5.2.0.M4.jar:5.2.0.M4]
at org.eclipse.scout.rt.platform.util.Assertions.assertFalse(Assertions.java:192) ~[org.eclipse.scout.rt.platform-5.2.0.M4.jar:5.2.0.M4]
at org.eclipse.scout.rt.client.ui.form.AbstractForm.setModal(AbstractForm.java:2700) ~[org.eclipse.scout.rt.client-5.2.0.M4.jar:5.2.0.M4]
at org.eclipse.scout.rt.client.ui.desktop.outline.pages.AbstractPage.decorateDetailForm(AbstractPage.java:692) ~[org.eclipse.scout.rt.client-5.2.0.M4.jar:5.2.0.M4]
I found solution for my problem.
in my constructor in form I have :
public PersonsExtensionFieldForm() {
this.startInternal(new NewHandler());
}
witch is ok, when you open form from other form with for example button click.
But when you put form in page as Detail form you need to have
public PersonsExtensionFieldForm() {
this.setHandler(new NewHandler());
}
I was reading a post at VS 2008, ASP.NET: Generate Local Resources.
Mehdi Golchin showed us a beautiful job of StateManagedCollection.
However I was wondered about using multiple classes of IStateManager in one StateManagedCollection.
As you can see below:
public class MenuItemCollection : StateManagedCollection
{
public MenuItem this[int index]
{
get { return (MenuItem)((IList)this)[index]; }
}
public int Add(MenuItem item)
{
return ((IList)this).Add(item);
}
public void Remove(MenuItem item)
{
((IList)this).Remove(item);
}
// Write Insert and RemoveAt methods
protected override void SetDirtyObject(object o)
{
((MenuItem)o).SetDirty();
}
}
This MenuItemCollection class can have only one child class("MenuItem").
If I want to use another class as well as MenuItem class, for example MenuItem2 class, how do I have to write the codes?
Anyone can help me?
Thanks in advance.
Write a generic version - for example,
public class GenericStateManagedCollection<T> : StateManagedCollection
where T: IStateManager, new()
{
public T this[int index]
{
get { return (T)((IList)this)[index]; }
}
public int Add(T item)
{
return ((IList)this).Add(item);
}
public void Remove(T item)
{
((IList)this).Remove(item);
}
// Write Insert and RemoveAt methods
protected override void SetDirtyObject(object o)
{
((T)o).SetDirty();
}
protected override object CreateKnownType(int index)
{
return Activator.CreateInstance<T>();
}
protected override Type[] GetKnownTypes()
{
return new Type[] { typeof(T) };
}
}
And use it as
public class MenuItemCollection : GenericStateManagedCollection<MenuItem> { }
public class XyzItemCollection : GenericStateManagedCollection<XyzItem> { }
EDIT:
I have most probably mis-understood your question! Assuming now that you want to put two different type of objects into the StateManagedCollection. From usage perspective, it doesn't make sense to have objects of completely unrelated types into the collection - you need to have some base class. For example, consider DataControlFieldCollection which holds instances of (abstract) type 'DataControField. BoundField, ButtonField etc inherits fromDataControField`.
So you need to go via similar route - for example,
public class MenuItemBase : IStateManager
{
// Use implementation from link you quoted (Mehdi Golchin's answer)
...
}
public class MenuItem : MenuItemBase
{
...
}
public class MenuItem2 : MenuItemBase
{
...
}
public class MenuItemCollection : StateManagedCollection
{
public MenuItemBase this[int index]
{
get { return (MenuItemBase)((IList)this)[index]; }
}
public int Add(MenuItemBaseitem)
{
return ((IList)this).Add(item);
}
public void Remove(MenuItemBaseitem)
{
((IList)this).Remove(item);
}
// Write Insert and RemoveAt methods
protected override void SetDirtyObject(object o)
{
((MenuItemBase)o).SetDirty();
}
// important to override CreateKnownType and GetKnownTypes
private static readonly Type[] _knownTypes = new Type[] {typeof(MenuItem), typeof(MenuItem2) }
protected override Type[] GetKnownTypes()
{
return _knownTypes;
}
protected override object CreateKnownType(int index)
{
switch (index)
{
case 0:
return new MenuItem();
case 1:
return new MenuItem2();
default:
throw new Exception("Invalid Index");
}
}
}
Note: Untested code