I want to create a simple option menu in Android application with c# and Xamarin Studio. How can I do it?
I haven't found any C# example of this. Can someone simply explain how to create a option menu, please?
Defining the menu
One way to create a menu is using a XML file placed in the Resources/menu/ folder of your Xamarin.Android project.
For example:
Resources/Menu/mymenu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/item1"
android:title="Item 1"/>
<item android:id="#+id/item2"
android:title="Item 2"/>
<item android:id="#+id/item3"
android:title="Item 3"/>
</menu>
To see what other options you can define in a menu xml file, please see the official documentation.
Using the menu
You can inflate a xml menu in multiple locations.
A few examples:
In a Toolbar
Android.Support.V7.Widget.Toolbar toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.mytoolbar);
toolbar.InflateMenu(Resource.Menu.mymenu);
Handle clicks
To handle click events on a toolbar menu you have to implement the Toolbar.IOnMenuItemClickListener interface by overriding the following method:
public bool OnMenuItemClick(IMenuItem item)
{
switch (item.ItemId)
{
case Resource.Id.item1:
//Do stuff for item1
return true;
case Resource.Id.item2:
//Do stuff for item2
return true;
case Resource.Id.item3:
//Do stuff for item3
return true;
default:
return false;
}
}
You then have to add the class implementing the interface to the toolbar as a listener:
toolbar.SetOnMenuItemClickListener(your_listener_class);
In the default menu location of an Activity or Fragment (DEPRECATED)
In most cases the default menu location of an activity or fragment is either the hardware menu button or the ActionBar.
Adding a menu here can be accomplished by the overriding the following method:
In a Activity:
public override bool OnCreateOptionsMenu(IMenu menu)
{
MenuInflater.Inflate(Resource.Menu.mymenu, menu);
return true;
}
In a Fragment:
public override void OnCreateOptionsMenu(IMenu menu, MenuInflater inflater)
{
inflater.Inflate(Resource.Menu.mymenu, menu);
}
Make sure you have HasOptionsMenu set to true in the onCreate of the Fragment for this to work.
Handle clicks
You can then handle clicks to the menu by overriding OnOptionsItemSelected
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.ItemId)
{
case Resource.Id.item1:
//Do stuff for item1
return true;
case Resource.Id.item2:
//Do stuff for item2
return true;
case Resource.Id.item3:
//Do stuff for item3
return true;
default:
return false;
}
}
After we have handled the selected item we return true to notify the system of this.
Alternative: Creating a menu programatically
A very basic menu is accomplished by overriding the OnCreateOptionsMenu method like this:
public override bool OnCreateOptionsMenu(IMenu menu)
{
menu.Add(0,0,0,"Item 0");
menu.Add(0,1,1,"Item 1");
menu.Add(0,2,2,"Item 2");
return true;
}
You can then handle clicks in the option menu by overriding the OnOptionsItemSelected method.
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.ItemId)
{
case 0:
//Do stuff for item 0
return true;
case 1:
//Do stuff for item 1
return true;
case 2:
//Do stuff for item 2
return true;
default:
return false;
}
}
Related
I'm trying to link the hardware back button into my WebViews in Xamarin for Android. My WebViews are contained within OnCreate instances of TabHost (which is deprecated, but I'm using it anyway) I've got this inside my MainActivity : TabActivity class
public override void OnBackPressed()
{
base.OnBackPressed();
}
and here's an example of one of my Tab Activity Classes
[Activity]
public class SpeakersActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
//set the content view
SetContentView(Resource.Layout.subs);
//declare webview and tell our code where to find the XAML resource
WebView subWebView = FindViewById<WebView>(Resource.Id.webViewSubs);
//set the webview client
subWebView.SetWebViewClient(new WebViewClient());
//load the subscription url
subWebView.LoadUrl("https://www.bitchute.com/subscriptions/");
//enable javascript in our webview
subWebView.Settings.JavaScriptEnabled = true;
//zoom control on? This should perhaps be disabled for consistency?
//we will leave it on for now
subWebView.Settings.BuiltInZoomControls = true;
subWebView.Settings.SetSupportZoom(true);
//scrollbarsdisabled
// subWebView.ScrollBarStyle = ScrollbarStyles.OutsideOverlay;
subWebView.ScrollbarFadingEnabled = false;
}
}
and I've seen a lot information how to use this
subWebView.GoBack();
to goback in a webview, but the problem is that my WebViews are not within the scope of my hardware back button. The hardware back button is inside mainactivity class and my webviews are inside individual instances of the tab activities.
What's the best way to correct this issue? Thank you!
Thanks so much #SushiHangover !!!
I solved it like this:
[Activity]
public class SpeakersActivity : Activity
{
public override void OnBackPressed()
{
WebView subWebView = FindViewById<WebView>(Resource.Id.webViewSubs);
subWebView.GoBack();
}
}
EDIT: the way you do this with a ViewPager and Fragment is as follows:
//put this code in your MainActivity.cs
public override bool OnKeyDown(Android.Views.Keycode keyCode, KeyEvent e)
{
if (e.KeyCode == Android.Views.Keycode.Back)
{
switch (_viewPager.CurrentItem)
{
case 0:
_fm1.WebViewGoBack();
break;
case 1:
_fm2.WebViewGoBack();
break;
case 2:
_fm3.WebViewGoBack();
break;
case 3:
_fm4.WebViewGoBack();
break;
case 4:
_fm5.WebViewGoBack();
break;
}
}
return false;
}
then in a fragment instantiate WebView assign in OnCreate and create a method for going back inside Fragment :
protected static WebView _wv;
protected static View _view;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
_view = inflater.Inflate(Resource.Layout.TheFragmentLayout1, container, false);
_wv = _view.FindViewById<WebView>(Resource.Id.webView1);
//lots of code
}
public void WebViewGoBack()
{
if (_wv.CanGoBack())
_wv.GoBack();
}
I have a context menu defined in my WPF XAML that looks like this:
<Window.Resources>
<ContextMenu x:Key="MyMenu">
<MenuItem Header="{x:Static props:Resources.MenuItem1}"/>
</ContextMenu>
</Window.Resources>
I'm using a System.Windows.Forms.NotifyIcon "myIcon" for my tray icon because it's so trivial to setup and use and because there seems to be no standard MSFT WPF equivalent. Unfortunately I get a casting exception when I call
this.myIcon.ContextMenu = (ContextMenu)this.Resources["MyMenu"];
because they're obviously not the same ContextMenu class. Is there a way to simply convert from the Controls.ContextMenu to a Forms.ContextMenu?
I'd prefer not to handle the right click mouse event of the notify icon by manually bringing up the context menu defined in my XAML. The reason being I suspect that the right click mouse event is not sent when the user uses the context menu key on the keyboard.
No - the controls are for completely different platforms (Winforms vs WPF). There is no "conversion" between the two.
Either use a different version of NotifyIcon that supports WPF or write a "conversion" that translates the items of the WPF context menu and adds them to the Winforms context menu.
So, I case anybody is curious. I ended up implementing a converter.
using System;
using System.Drawing;
using System.Windows.Controls;
namespace MyApp
{
class NotifyIconEx
{
#region Data
private System.Windows.Forms.NotifyIcon _notifyIcon = new System.Windows.Forms.NotifyIcon();
#endregion // Data
#region Properties
public Icon Icon
{
get { return _notifyIcon.Icon; }
set { _notifyIcon.Icon = value; }
}
public ContextMenu ContextMenu
{
private get { return null; }
set
{
_notifyIcon.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();
foreach (var item in value.Items)
{
if (item is MenuItem)
{
var menuItem = item as MenuItem;
var toolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
toolStripMenuItem.Click += (s, e) => menuItem.RaiseEvent(new System.Windows.RoutedEventArgs(MenuItem.ClickEvent));
toolStripMenuItem.Text = menuItem.Header as string;
_notifyIcon.ContextMenuStrip.Items.Add(toolStripMenuItem);
}
else if (item is Separator)
{
_notifyIcon.ContextMenuStrip.Items.Add(new System.Windows.Forms.ToolStripSeparator());
}
else
{
throw new NotImplementedException();
}
}
}
}
public bool Visible
{
get { return _notifyIcon.Visible; }
set { _notifyIcon.Visible = value; }
}
#endregion // Properties
#region API
public void ShowBalloonTip(int timeout)
{
_notifyIcon.ShowBalloonTip(timeout);
}
public void ShowBalloonTip(int timeout, string tipTitle, string tipText, System.Windows.Forms.ToolTipIcon tipIcon)
{
_notifyIcon.ShowBalloonTip(timeout, tipTitle, tipText, tipIcon);
}
#endregion // API
}
}
I have a MainActivity which has 3 fragments. 2 of these fragments have lists in them and on click of a list item I go to another activity (ie. NewActivity). I have an ActionBar implemented on the NewActivity and want to go back to the calling fragment from this activity.
I have the following code:
MainActivity.cs
SupportActionBar.SetDisplayHomeAsUpEnabled(true);
SupportActionBar.SetDisplayShowHomeEnabled(true);
var ft = SupportFragmentManager.BeginTransaction();
ft.Add(Resource.Id.fragment_container, homeFragment, "FRAG_HOME").SetBreadCrumbShortTitle("FRAG_HOME");
ft.Add(Resource.Id.fragment_container, FragmentList1, "FRAG_LIST1").SetBreadCrumbShortTitle("FRAG_LIST1");
ft.Hide(FragmentList1);
ft.Add(Resource.Id.fragment_container, FragmentList2, "FRAG_LIST2").SetBreadCrumbShortTitle("FRAG_LIST2");
ft.AddToBackStack(null);
ft.Hide(FragmentList2);
ft.Commit();
FragmentList1.cs
void LvLatestArticles_ItemClick (object sender, AdapterView.ItemClickEventArgs e)
{
Intent intent = new Intent(Activity, typeof(NewActivity));
StartActivity(intent);
}
NewActivity.cs
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch(item.ItemId)
{
case Android.Resource.Id.Home:
SupportFragmentManager.PopBackStackImmediate();
break;
}
return base.OnOptionsItemSelected(item);
}
In the above code, I override the Home button functionality on ActionBar. But it simply does not do anything. How do I go forward with this? Any help would be appreciated
Try calling OnBackPressed() on the Home button. Something like so :-
public override bool OnOptionsItemSelected(IMenuItem item){
switch(item.ItemId){
case Android.Resource.Id.Home:
base.OnBackPressed();
break;
}
return base.OnOptionsItemSelected(item);
}
I'm about to implement NavDrawer on android using Mvvmcross and Xamarin (in c#), but I'm a bit worried about having my HomeView model look like this: https://github.com/jamesmontemagno/Xam.NavDrawer/blob/master/Mvx/MvxSample/ViewModels/HomeViewModel.cs.
How would I use a view model like that on a MS platform, where I would probably have a panarama or something very different to layout the data.
Should I perhaps have a separate home viewmodel for platforms that don't support a NavDrawer type experience?
Navigation Bar implementation for api level > 7
NavDrawerProject/res/values/strings.xml
<resources>
<string-array name="pages_array">
<item>Home</item>
<item>Trending</item>
<item>Profile</item>
</string-array>
<resources>
NavDrawerProject/res/layout/activity_main.xml
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="#+id/left_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#color/drawer_bg"
android:paddingTop="?attr/actionBarSize"/>
</android.support.v4.widget.DrawerLayout>
NavDrawerProject/src/MainActivity.java
public class MainActivity extends ActionBarActivity {
// navigation drawer
private DrawerLayout mDrawerLayout;
private ListView mDrawerList;
private ActionBarDrawerToggle mDrawerToggle;
private CharSequence mDrawerTitle;
private CharSequence mTitle;
private String[] mPageTitles;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setting up navigation drawer
mTitle = mDrawerTitle = getTitle();
mPageTitles = getResources().getStringArray(R.array.pages_array);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerList = (ListView) findViewById(R.id.left_drawer);
// set a custom shadow that overlays the main content when the drawer
// opens
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow,
GravityCompat.START);
// set up the drawer's list view with items and click listener
mDrawerList.setAdapter(new ArrayAdapter<String>(this,
R.layout.drawer_list_item, mPageTitles));
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
// enable ActionBar app icon to behave as action to toggle nav drawer
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
// ActionBarDrawerToggle ties together the the proper interactions
// between the sliding drawer and the action bar app icon
mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */
mDrawerLayout, /* DrawerLayout object */
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
R.string.drawer_open, /* "open drawer" description for accessibility */
R.string.drawer_close /* "close drawer" description for accessibility */
) {
// TODO find out if stray {
public void onDrawerClosed(View view) {
getSupportActionBar().setTitle(mTitle);
supportInvalidateOptionsMenu(); // creates call to
// onPrepareOptionsMenu()
}
public void onDrawerOpened(View drawerView) {
getSupportActionBar().setTitle(mDrawerTitle);
supportInvalidateOptionsMenu(); // creates call to
// onPrepareOptionsMenu()
}
// TODO find out if stray
};
mDrawerLayout.setDrawerListener(mDrawerToggle);
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
// If the nav drawer is open, hide action items related to the content
// view
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
menu.findItem(R.id.action_settings).setVisible(!drawerOpen);
return super.onPrepareOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// The action bar home/up action should open or close the drawer.
// ActionBarDrawerToggle will take care of this.
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
}
/* The click listener for ListView in the navigation drawer */
private class DrawerItemClickListener implements
ListView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
selectItem(position);
}
}
private void selectItem(int position) {
// update the main content by replacing fragments
Fragment fragment;
switch (position) {
case 0:
fragment = new HomePageFragment();
break;
case 1:
fragment = new TrendingPageFragment();
break;
case 2:
fragment = new ProfilePageFragment();
break;
default:
fragment = new HomePageFragment();
break;
}
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.content_main, fragment);
transaction.addToBackStack(null);
transaction.commit();
// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
setTitle(mPageTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
#Override
public void setTitle(CharSequence title) {
mTitle = title;
getSupportActionBar().setTitle(mTitle);
}
}
To use Navigation Drawer on old Android, you should take a look at Support Library.
http://components.xamarin.com/view/xamandroidsupportv7appcompat
See the picture above. This is a screenshot from Visual Studio's options form.
The left side is essentially a TreeView. The right side is various controls that change program options.
When nodes in the TreeView are selected, the right side changes, showing different options.
How do you program something like this? Does the right side just have 50 overlapping panels, and the selecting of nodes just changes which panel is visible? If this is the case, how would you go about managing such? It would be a mess in the designer.
No you don't make 50 overlapping panels. Just create several usercontrols and, for example, link the types on the tag of a node. You can use the Activator to create the controls.
Create 1 treeview and 1 panel: (PSEUDO CODE)
// create nodes:
TreeNode item = new TreeNode();
item.Tag = typeof(UserControl1);
TreeView.Nodes.Add( item );
// field currentControl
UserControl _currentControl;
// on selection:
TreeViewItem item = (TreeViewItem)sender;
if(_currentControl != null)
{
_currentControl.Controls.Remove(_currentControl);
_currentControl.Dispose();
}
// if no type is bound to the node, just leave the panel empty
if (item.Tag == null)
return;
_currentControl = (UserControl)Activator.Create((Type)item.Tag);
Panel1.Controls.Add(_currentControl);
The next question would be, "I'd like to call a save method, or RequestClose method in the controls". For this, you should implement an Interface on the controls, and when you switch nodes, just try to cast the _currentusercontrol to IRequestClose interface and call, for example, bool RequestClose(); method.
// on selection:
TreeViewItem item = (TreeViewItem)sender;
if(_currentControl != null)
{
// if the _currentControl supports the IRequestClose interface:
if(_currentControl is IRequestClose)
// cast the _currentControl to IRequestCode and call the RequestClose method.
if(!((IRequestClose)_currentControl).RequestClose())
// now the usercontrol decides whether the control is closed/disposed or not.
return;
_currentControl.Controls.Remove(_currentControl);
_currentControl.Dispose();
}
if (item.Tag == null)
return;
_currentControl = (UserControl)Activator.Create(item.Tag);
Panel1.Controls.Add(_currentControl);
But this will be the next step.
For me, the common design of that is, a classical treeview on the left side and a "content zone" on the right side. When the user pick something in the treeview you load the related view in the content zone. After there's a lot of different way to implement the stuff, for example automaticaly generate the treeview based on a list of object which contain the type of view to be instanciated and create a generic instantiator called when an item is picked to create the related view, anyway, the background is still the same. To resume, a treeview and just create the view in the content zone based on the selected item. (I've seen several screen like that in my work and most of the time it was like that)
My approach, after checking several options, was to inherit the TabControl component in such a way the pages of the control can be used as paged panels, and adding functionality so that the tabs do not show at run time. Then, by creating a property called Pages which depends on TabPages, I can refer to each page in a semantically correct way, giving the advantage of being able to manage every page as part of the Pages collection, and also hierarchically through the document explorer.
The code also hides design-time properties that pertain to a regular TabControl, but that would be irrelevant in a paged panel. Below is the code if anyone is interested.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
namespace MyCustomControls
{
public class PagedPanel : TabControl
{
//------------------------------------------------------------------------------------------------
public PagedPanel()
{
base.Multiline = true;
base.Appearance = TabAppearance.Buttons;
base.ItemSize = new Size(0, 1);
base.SizeMode = TabSizeMode.Fixed;
base.TabStop = false;
}
//------------------------------------------------------------------------------------------------
protected override void WndProc(ref Message m)
{
// Hide tabs by trapping the TCM_ADJUSTRECT message
if (m.Msg == 0x1328 && !DesignMode) m.Result = (IntPtr)1;
else base.WndProc(ref m);
}
//------------------------------------------------------------------------------------------------
protected override void OnKeyDown(KeyEventArgs ke)
{
// Block Ctrl+Tab and Ctrl+Shift+Tab hotkeys
if (ke.Control && ke.KeyCode == Keys.Tab)
return;
base.OnKeyDown(ke);
}
//------------------------------------------------------------------------------------------------
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[DefaultValue(true)]
public new bool Multiline
{
get { return base.Multiline; }
set { base.Multiline = value; Invalidate(); }
}
//------------------------------------------------------------------------------------------------
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)
, DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[DefaultValue(TabAppearance.Buttons)]
public new TabAppearance Appearance
{
get { return base.Appearance; }
set { base.Appearance = value; Invalidate(); }
}
//------------------------------------------------------------------------------------------------
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)
, DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[DefaultValue(typeof(Size), "0, 1")]
public new Size ItemSize
{
get { return base.ItemSize; }
set { base.ItemSize = value; Invalidate(); }
}
//------------------------------------------------------------------------------------------------
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)
, DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[DefaultValue(TabSizeMode.Fixed)]
public new TabSizeMode SizeMode
{
get { return base.SizeMode; }
set { base.SizeMode = value; Invalidate(); }
}
//------------------------------------------------------------------------------------------------
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)
, DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new TabPageCollection TabPages
{
get { return base.TabPages; }
}
//------------------------------------------------------------------------------------------------
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[DefaultValue(false)]
public new bool TabStop
{
get { return base.TabStop; }
set { base.TabStop = value; Invalidate(); }
}
//------------------------------------------------------------------------------------------------
public TabPageCollection Pages
{
get { return base.TabPages; }
}
//------------------------------------------------------------------------------------------------
}
}
The treeview would handle calling each tab either by key or index, a relatively trivial task. I do this by naming the nodes in my tree with a prefix such as "tvn", and then naming the pages in the PagedPanel the same but with prefix "pg". So on the AfterSelect event of the treeview, all I need is the name of the current node and I know what page to show.