piątek, 6 lutego 2015

Przecieki - Windows 10 build 10009, Windows 10 for phones

I tak mamy kolejny weekend, który znów przywitamy przeciekami związanymi z Windows 10.

20150207_175130_760

W Windows 10 w wersji 10009 mamy nowe ikonki, nareszcie płaski kosz, a nie taki starodawny z przezroczystościami …i Spartana. Poza tym ogólnie w “dziesiątce” więcej aplikacji obsługuje ciemne i jasne skórki.

20150206_120113_827

Trwają prace nad tym by Windows 10 for phones wspierał urządzenia z 512 MB ramu, mogą być różnice w funkcjonalności w stosunku do 1GB. Wersja Technical Preview trafi najpierw tylko na część telefonów z Windows Phone. Przewidywana jest możliwość powrotu do obecnej wersji systemu jakby ktoś nie był zadowolony z preview. Poza tym wyciekło trochę więcej screenshotów, na których można zobaczyć m.in nową aplikację do nagrywania dźwięku i wbudowany menadżer plików, a także UI i uniwersalne aplikacje kojarzące się z dotychczas udostępnioną dużą “dziesiątką”.

Cortana jest integrowana z Office.  Dowodem na to jest aplikacja “Work Assistant”, która ostatnio została nagrana w akcji:

środa, 4 lutego 2015

Windows 10, C#, .NET - informacje dnia

Dokonajmy kolejnego zestawienia newsów, tym razem z końca dnia wczorajszego i z dziś.

  1. News dnia - udostępnienie aplikacji Modern Word, Excel i PowerPoint użytkownikom Windows 10.
  2. Drugi news - ukazał się patch do Windows 10
  3. Kolejne ważne wydarzenie  - udostępnienie Core CLR jako Open Source, na 18 i 19 marca została zapowiedziana wirtualna konferencja dotNetConf
  4. Pierwsze tematy związane z C# 7: obsługa danych - inspiracje z języków funkcyjnych i innych języków tj. F#, Scala, Swift;  wydajność (i interop); komponenty; systemy rozproszone - asynchroniczne sekwencje i serializacja; metaprogramowanie; null oraz funkcjonalności:  dopasowywanie wzorca, rekordy, array slices, ref dla zmiennych lokalnych i zwracanych, lista przechwyceń w wyrażeniach lambda (rozważane atrybuty), kontrakty dla metod.
  5. Plotki na temat Windows 10 Technical Preview for phones - udostępnienie wersji 8.15.12492.41 dla zamkniętego kręgu testerów, duży kafelek w orientacji pionowej
  6. Powracają spiskowe teorie na temat rozwoju platformy aplikacji w Windows Store, tym razem w oparciu o ogłoszenie o pracę. Postulat ułatwienia życia deweloperom połączony ze znaczącym zwiększeniem ilości aplikacji może kojarzyć się z pomysłem inkorporacji innych platform, zwłaszcza Androida do ekosystemu Windows.

Linki:

wtorek, 3 lutego 2015

Kulisy Xamarina - odc. 3: jeszcze trochę o Androidzie

Trochę informacji uzupełniających odnośnie Androida i Xamarina. W ogólnym ujęciu należy podkreślić, że obok wynikowej dll-ki generowane są wrappery w postaci klas Java i w konsekwencji plik APK. W szczegółach nic nadzwyczajnego, kolejne przykłady na używanie w C# funkcjonalności znanych z Android SDK (Java).

 

Ogólne uzupełnienia

Aktywności Java i C# mogą się wzajemnie wywoływać, po obu stronach mamy odpowiedniki.

Z assembly .NET generowane są wrappery w Javie (JNI) i budowany jest plik APK.

.NET BCL + Android SDK + Xamarin.Mobile (np. geolokalizacja)

Możliwość używania bibliotek  Javy i .NET

http://developer.xamarin.com/releases/android/

 

Drobne uzupełnienia

Odczytanie napisu z zasobów:

GetString(Resource.String.Xyz)

Wypisanie na konsolę (Output w VS):

Console.WriteLine(“xxx”);

Tworzenie adaptera z tablicy stringów w zasobach (dla Spinner):

var adapter = ArrayAdapter.CreateFromResource(this, Resource.Array.categories, Android.Resource.Layout.SimpleSpinnerItem);

adapter.SetDropDownViewResource(Android.Resource.Layout.SimpleSpinnerDropDownItem);

Pobranie elementu Spinnera o danym indeksie:

spinner.GetItemAtPosition(position);

 

Ustawienia

Plik xml w Resources/Xml

[Activity(Label = “Preferences”)]

public class PreferencesActivity: PreferenceActivity

{

         protected override void OnCreate(Bundle bundle)

         {

                 base.OnCreate(bundle);

                 AddPreferencesFromResource(Resource.Xml.preferences);

         }

}

 

[Activity(Label = “XXX”, MainLauncher = true, Icon = “@drawable/icon”)]

public class XActivity: Activity

{

         …

         protected override void OnResume()

         {

                  base.OnResume();

                  var preferences = PreferenceManager.GetDefaultPreferences(ApplicationContext);

                  var x = preferences.GetString(“x”, “0”);

                  …

         }

         //zapis ustawień

         …

         var preferences = PreferenceManager.GetDefaultPreferences(ApplicationContext);

         var editor = preferences.Edit();

          editor.PutString(“x”, “0”);

          editor.Commit();

}

 

Menu

option menu

plik XML w Resources/Menu

 

[Activity(Label = “XXX”, MainLauncher = true, Icon = “@drawable/icon”)]

public class XActivity: Activity

{

         …

         public override bool OnCreateOptionsMenu(IMenu menu)

         {

                   MenuInflater.Inflate(Resource.Menu.optionsmenu, menu);

                   menu.Add(“YYY”);

                   return true;

         }

         public override bool OnOptionsItemSelected(IMenuItem item)

         {

                  //item.TitleFormatted

                  switch(item.ItemId)

                  {

                           case Resource.Id.xxx:

                                      …

                                      return true;

                            default:

                                       return base.OnOptionsItemSelected(item);

                  }               

         }

}

menu kontekstowe

plik XML w Resources/Menu

 

[Activity(Label = “XXX”, MainLauncher = true, Icon = “@drawable/icon”)]

public class XActivity: Activity

{

        Button button;

         …

         protected override void OnCreate(Bundle bundle)

         {

                  …

                  RegisterForContextMenu(button);

          }

          public override void OnCreateContextMenu(IContextMenu menu, View v,

          IContextMenuContextMenuInfo menuInfo)

          {

                   base.OnCreateContextMenu(menu, v, menuInfo);

                   if (v == button)

                   {

                           MenuInflater.Inflate(Resource.Menu.contextmenu, menu);

                   }

          }

          public override bool OnContextItemSelected(IMenuItem item)

          {

                    switch(item.ItemId)

                    {

                           case Resource.Id.Aaa:

                                  …

                                  //przy wybieraniu:  item.SetChecked(true);

                                  return true;

                            default:

                                  return base.OnContextItemSelected(item);

                    }

          }

   }

 

Dialogi

[Activity(Label = “XXX”, MainLauncher = true, Icon = “@drawable/icon”)]

public class XActivity: Activity

{

        private int const ALERT_DIALOG = 0;

        …

        protected override Dialog OnCreateDialog(int id)

        {

                 switch(id)

                 {

                         case ALERT_DIALOG:

                                  var builder = new AlertDialog.Builder(this);                                 

                                  /* return builder.SetTitle(“Xxxx ?”)

                                          .SetMessage(“Yyyy zzzz aaa bbb ?”)

                                          .SetCancelable(false)

                                          .SetPositiveButton(“OK”, (o, args) => { … })

                                          .SetNegativeButton(“No”, (o, args) => { … })

                                          .Create(); */                                  

                                   

                                   //.SetItems(), .SetMultiChoiceItems()

                                   return builder.SetTitle(“Xxxx ?”)                                        

                                          .SetCancelable(false)

                                          .SetPositiveButton(“OK”, (o, args) => { … })

                                          .SetNegativeButton(“No”, (o, args) => { … })

                                          .SetItems(new [] { “XXX”, “YYY”, “ZZZ”},  (o, args) => {  //args.Which   })

                                          .Create();

                 }

                 return null;

        }

        …

        ShowDialog(ALERT_DIALOG);   

        …

        var progressDialog = ProgressDialog.Show(this, “Tytuł”, “Wiadomość”);

        ThreadPool.QueueUserWorkItem(s =>

        {

                 Thread.Sleep(5000);

                 RunOnUiThread(() => progressDialog.Dismiss());

        });

}

niestandardowy dialog

plik XML w Resources/Layout

w OnCreateDialog:

var dialog = new Dialog(this);

dialog.SetContentView(Resource.Layout.customdialog);

var button = (Button) dialog.FindViewById(Resource.Id.customButton);

button.Text = “XXX”;

return dialog;

DatePicker / TimePicker dialog

w OnCreateDialog:

var dateDialog = new DatePickerDialog(this, (o, args) => { /*args.Date*/ }, 2015, 2, 3);

return dateDialog;

 

Publikacja

http://developer.xamarin.com/guides/android/deployment,_testing,_and_metrics/publishing_an_application/part_1_-_preparing_an_application_for_release/

Manifest Android w katalogu Properties projektu

czwartek, 29 stycznia 2015

Kulisy Xamarina - odc. 2: nawigacja, selektory, protokoły

Wchodzimy na kolejny level Xamarina (nie ostatni) dotyczący: różnych rodzajów nawigacji, obsługi list i selektorów oraz protokołów w iOS, które są czymś więcej niż interfejsami w C#. Ta ostatnia rzecz jest przykładem, że o ile Xamarin stara się dość wiernie mapować natywne API, o tyle nie robi tego za wszelką cenę i czasami idzie na pewien kompromis proponując własne bardziej wygodne w C# odpowiedniki.

 

Nawigacja Swipe w Android

ViewPager + fragmenty z zawartością stron + adapter (bazujący na FragmentStatePagerAdapter)

Tworzymy:

  • plik .axml dla fragmentu
  • klasę C# dla fragmentu (szablon Fragment)

public class AlbumFragment: Fragment

{

        TextView textTitle;

        …

        public override void OnCreate(Bundle savedInstanceState)

        {

                  base.OnCreate(savedInstanceState);

                  …

        }

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container,

        Bundle savedInstanceState)

        {

                  View rootView = inflater.Inflate(Resource.Layout.AlbumFragment, container, false);

 

                  textTitle = rootView.FindViewById<TextView>(Resource.Id.textTitle);

                  …

 

                  return rootView;

        }

}

Z Android.Support.V4.App potrzebujemy klas FragmentStatePagerAdapter i ViewPager, a także FragmentActivity i Fragment (nawet jeśli urządzenie wspiera natywnie fragmenty). W Xamarin nie dodajemy referencji do Mono.Android.Support.v4. Zamiast tego w folderze Components wybieramy podręczne polecenie Get More Components…, otwiera się nam strona Web ze sklepem Xamarin(darmowe lub płatne dodatki), z której możemy pobrać Android Support Library v4 (w referencjach pojawia się referencja do Xamarin.Android.Support.v4). W podobny sposób dodajemy też Android Support Library v7 AppCompat.

class AlbumPagerAdapter: FragmentStatePagerAdapter

{

         …

         public override Android.Support.V4.App.Fragment GetItem(int position)

         {

                  …

                  AlbumFragment albumFragment = new AlbumFragment();

                  …

         }

          public override int Count

          {

          }

}

W pliku z definicją fragmentu zmieniamy namespace z Android.App na Android.Support.V4.App.

Tworzymy nowy layout (szablon Android Layout):

<?xml version=”1.0” encoding=”utf-8”?>

<android.support.v4.view.ViewPager xmlns:android=”…”

   android.id = “@+id/albumPager”

   android:layout_width=”fill_parent”

   android:layout_height=”fill_parent”/>

a potem aktywność zastępującą poprzednią główną aktywność:

[Activity(Label = “Albums”, MainLauncher = true, Icon = “@drawable/icon”)]

public class AlbumActivity: FragmentActivity

{

           …

           AlbumPagerAdapter albumPagerAdapter;

           ViewPager viewPager;

           protected override void OnCreate(Bundle bundle)

           {

                     base.OnCreate(bundle);

                     SetContentView(Resource.Layout.CourseActivity);

                     …

                     albumPagerAdapter = new AlbumPagerAdapter(SupportFragmentManager, …);

                     viewPager = FindViewById<ViewPager>(Resource.Id.albumPager);

                     viewPager.Adapter = albumPagerAdapter;

           }

}

 

Nawigacja Swipe w iOS

UIPageViewController + instancje UIViewController do poszczególnych stron

Tworzymy AlbumPagerViewController (szablon UIViewController class):

   [Register("AlbumViewController")]
   public class AlbumViewController : UIViewController
   {

       UIPageViewController pageViewController;

       …


       public AlbumViewController()
       {
       }

       public override void DidReceiveMemoryWarning()
       {
           base.DidReceiveMemoryWarning();

       }

       public override void ViewDidLoad()
       {          

           base.ViewDidLoad();

           pageViewController = new UIPageViewController(

                UIPageViewControllerTransitionStyle.Scroll,

                UIPageViewControllerNavigationOrientation.Horizontal);

           pageViewController.View.Frame = View.Bounds;

           this.View.AddSubview(pageViewController.View);

           var firstAlbumViewController = CreateAlbumViewController();

           pageViewController.SetViewControllers(

                 new UIViewController[] { firstAlbumViewController },

                 UIPageViewControllerNavigationDirection.Forward, false, null);

            pageViewController.GetNextViewController = GetNextViewController;

            pageViewController.GetPreviousViewController = GetPreviousViewController;

        }

        AlbumViewController CreateAlbumViewController()

        {

                 var albumViewController = new AlbumViewController();

                 …

        }

        public UIViewController GetNextViewController(

                 UIPageViewController pageViewController,

                 UIViewController referenceViewController)

        {

                 …

        }

        public UIViewController GetPreviousViewController(

                 UIPageViewController pageViewController,

                 UIViewController referenceViewController)

        {

                 …

        }

       
   }

W AppDelegate.cs zmieniamy główny kontroler (lub robimy to w bardziej nowoczesny sposób).

Przygotowujemy kontroler, który ma prezentować zawartość stron.

Animacja przewijania strony zamiast swipe:

pageViewController = new UIPageViewController(

        UIPageViewControllerTransitionStyle.PageCurl,

        UIPageViewControllerNavigationOrientation.Horizontal,

        UIPageViewControllerSpineLocation.Min);

 

Używanie protokołów z iOS

Xamarin czasami ukrywa używanie protokołów

  • czasami oferuje alternatywy do jawnego używania protokołów
    • może dodawać składowe (propercje delegata z nieco innymi nazwami) do klasy (by ukryć korzystanie z protokołu)
    • przykład: UIPageViewController (ukrywa użycie protokołu i nadal wspiera jego używanie)

Protokoły Objective-C mają właściwości niedostępne w interfejsach C#

  • opcjonalne składowe

Wyzwanie

  • implementacja typów realizujących kontrakt z protokołu
  • nie możemy użyć tutaj interfejsów

Implementacja protokołów w Xamarin

  • abstrakcyjne klasy
    • wymagane składowe są oznaczane jako abstract
    • opcjonalne składowe są oznaczane jako virtual

Implementujemy klasę przez dziedziczenie i dołączamy jako referencję (klasa używająca protokołu ma już klasę bazową)

class AlbumPagerViewControllerDataSource: UIPageViewControllerDataSource

{

         …

         public override UIViewController GetNextViewController(UIPageViewController pageViewController)

         {

         }

         public override UIViewController GetPreviousViewController(UIPageViewController pageViewController)

         {

         }

}

   [Register("AlbumViewController")]
   public class AlbumViewController : UIViewController
   {

       UIPageViewController pageViewController;

       …  

       public override void ViewDidLoad()
       {   

               …

               var dataSource = new AlbumPagerViewControllerDataSource(…);

               pageViewController.DataSource = dataSource;

               …                     

       }        
   }

 

Nawigacja master-detail w Android

Każda aktywność wymaga atrybutu [Activity(Label = “opis”)]

Lista:

[Activity(Label = “AlbumCategories”, MainLauncher = true, Icon = “@drawable/icon”)]

public class CategoryActivity: ListActivity

{

          protected override void OnCreate(Bundle bundle)

          {

                   base.OnCreate(bundle);

                   var categories = { “Rock”, “Classic”, “Jazz” };

                   ListAdapter = new ArrayAdapter<String>(this,  Android.Resource.Layout.ListItem, categories);

          }

}

Niestandardowy adapter:

class AlbumCategoryAdapter: BaseAdapter<AlbumCategory>

{

           Context context;

           int layoutResourceId;

           …

           public AlbumCategoryAdapter(Context context, int layoutResourceId, …)

           {

                   this.context = context;

                   this.layoutResourceId = layoutResourceId;

                   …

           }

           public override AlbumCategory this[int position]

           {

                    get { … }

           }

           public override int Count

           {

                    get { … }

           }

           public override long GetItemId(int position)

           {

                    return position;

           }

           public override View GetView(int position, View convertView, ViewGroup parent)

           {

                    View view = convertView;

                    if (view == null)

                    {

                            var inflater = context.GetSystemService(Context.LayoutInflaterService) as LayoutInflater;

                            view = inflater.Inflate(layoutResourceId, null);

                    }

                    var textView = view.FindViewById<TextView>(Android.Resource.Id.Text1);

                    textView.Text = this[position].Title;

                    return view;

           }

}

 

[Activity(Label = “AlbumCategories”, MainLauncher = true, Icon = “@drawable/icon”)]

public class CategoryActivity: ListActivity

{

          …

          protected override void OnCreate(Bundle bundle)

          {

                   base.OnCreate(bundle);

                   …

                   ListAdapter =

                   new AlbumCategoryAdapter(this, Android.Resource.Layout.ListItem, …);

          }

          protected override void OnListItemClick(ListView l, View v, int position, long id)

          {

                   var intent = new Intent(this, typeof(AlbumActivity));

                   …

                   intent.PutExtra(“extra name”, categoryName);

                   StartActivity(intent);

          }

}

 

[Activity(Label = “Albums”)]

public class AlbumActivity: FragmentActivity

{

           …

           protected override void OnCreate(Bundle bundle)

           {

                     …

                     var startupIntent = this.Intent;

                     if (startupIntent != null)

                     {

                              string categoryName = startupIntent.GetStringExtra(“extra name”);

                              …

                     }

                     …

            }

}

 

Android Navigation Drawer

<?xml version=”1.0” encoding=”utf-8” ?>

<android.support.v4.widget.DrawerLayout  xmlns:android=”…”

   android.id=”@+id/drawerLayout”

   android:layout_width=”match_parent”

   android:layout_height=“match_parent”>

   <android.support.v4.view.ViewPager

           android.id=”@+id/albumPager”

           android:layout_width=”fill_parent”

           android:layout_height=”fill_parent” />

    <ListView

           android:id=”@+id/categoryDrawerListView”

           android:layout_height=”match_parent”

           android:layout_width=”240p”

           android:layout_gravity=”start”

           android:choiceMode=”singleChoice”

           android:divider=”@android:color/transparent”

           android:dividerHeight=”0dp”

           android:background=”#888” />

</android.support.v4.widget.DrawerLayout>

 

[Activity(Label = “Albums”, MainLauncher = true, Icon = “@drawable/icon”)]

public class AlbumActivity: FragmentActivity

{

           …

           DrawerLayout drawerLayout;

           ListView categoryDrawerListView;

 

           protected override void OnCreate(Bundle bundle)

           {

                     …

                     drawerLayout = FindViewById<DrawerLayout>(Resource.Id.drawerLayout);

                     categoryDrawerListView = FindViewById<ListView>(Resource.Id.categoryDrawerListView);

                     categoryDrawerListView.Adapter =

                     new AlbumCategoryAdapter(this, Android.Resource.Layout.ListItem, …);

                     categoryDrawerListView.SetItemChecked(0, true);

                     categoryDrawerListView.ItemClick += categoryDrawerListView_ItemClick;

            }

            void categoryDrawerListView_ItemClick(object sender, Adapter.ItemClickEventArgs e)

            {

                     drawerLayout.CloseDrawer(categoryDrawerListView);

                     //e.Position  - przeładowanie danych

                     …

                     viewPager.CurrentItem = 0;

            }

}

 

Właściwości elementów w designerze:

@android:id/text1

?android:attr/activatedBackgroundIndicator

 

class AlbumPagerAdapter: FragmentStatePagerAdapter

{

         …

         public override int GetItemPosition(Java.Lang.Object @object)

         {

                 return PositionNone;   //wymuszenie ponownego tworzenia elementów

         }

         //odświeżenie danych: NotifyDataSetChanged();

}

 

Nawigacja master-detail w iOS

[Register("CategoryViewController")] 
public class CategoryViewController : UITableViewController 
{

        …

        public CategoryViewController()

        {

        }

        public override void DidReceiveMemoryWarning()
        {
                base.DidReceiveMemoryWarning();

        }

        public override void ViewDidLoad()
        {  

                 base.ViewDidLoad(); 

                 this.Title = “Kategorie”;

                 …

                 UITableView tableView = this.View as UITableView; 

                 tableViewSource.Source = new CategoryViewSource(…);  

        }

}

 

class CategoryViewSource: UITableViewSource

// zamiast: UITableViewDataSource i UITableViewDelegate

{

         const string cellId = “Cell”;

         …

         public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)

         {

                  UITableViewCell cell = tableView.DequeueReusableCell(cellId);

                  if (cell == null)

                         cell = new UITableViewCell(UITableViewCellStyle.Default, cellId);

                  //indexPath.Row

                  cell.TextLabel.Text = …

                  return cell;

         }

         public override int RowsInSection(UITableView tableView, int section)

         {

                  …

         }

         //przy nawigacji za pomocą kodu

         public override void RowSelected(UITableView tableView, NSIndexPath indexPath)

         {

                  var albumPagerViewController = new AlbumPagerViewController(…);  //indexPath.Row

                  var appDelegate = UIApplication.SharedApplication.Delegate as AppDelegate;

                  appDelegate.RootNavigationController.PushViewController(albumPagerViewController, true);

         }

}

 

//przy nawigacji za pomocą kodu

public partial class AppDelegate: UIApplicationDelegate

{

          …

          public UINavigationController RootNavigationController

          {

                   get;

                   private set;

          }

          public override bool FinishedLaunching(UIApplication app, NSDictionary options)

          {

                   …

                   RootNavigationController = new UINavigationController();

                   var viewController = new CategoryViewController();

                   RootNavigationController.PushViewController(viewController, false);

                   window.RootViewController = viewController;

                   …

                   return true;

          }

}

 

Uzupełnienia

Android

Combo - podłączenie

  • kontrolka Spinner

spinner.Adapter = new ArrayAdapter<string>(this, Android.Resource.Layout.ListItem, array);

spinner.ItemSelected += (object sender, AdapterView.ItemSelectedEventArgs e) =>

{

        //e.Position - wybrany indeks

};

Notyfikacja

Toast.MakeText(this, “XXX”, ToastLength.Short).Show();

 

iOS

UIPickerView - podłączenie

public abstract class GenericPickerModel<T>:  UIPickerViewModel

{

         public T SelectedItem { get; private set; }

         public event EventHandler ItemSelected;

 

         IList<T> _items;

         public IList<T> Items

         {

                  get { return _items; }

                  set { _items = value;  Selected(null, 0, 0); }

         }

         public GenericPickerModel()

         {

         }

         public GenericPickerModel(IList<T> items)

         {

                 Items = items;

         }

         public override int GetRowsInComponent(UIPickerView picker, int component)

         {

                  if (NoItem())

                         return 1;

                  return Items.Count;

         }

         public override string GetTitle(UIPickerView picker,  int row,  int component)

         {

                  if (NoItem(row))

                        return “”;

                  var item = Items[row];

                  return GetTitleForItem(item);

         }

         public override void Selected(UIPickerView picker, int row, int component)

         {

                  if (NoItem(row))

                         SelectedItem = default(T)

                  else

                         SelectedItem = Items[row];

                  if (ItemSelected != null)

                         ItemSelected(this, null);

         }

         public override int GetComponentCount(UIPickerView picker)

         {

                  return 1;

         }

         public virtual string GetTitleForItem(T item)

         {

                  return item.ToString();

         }

         bool NoItem(int row = 0)

         {

                  return Items == null || row >= Items.Count;

         }

}

 

public class DocumentPickerModel: GenericPickerModel<Document>

{

         public DocumentPickerModel(IList<Document> documents): base(documents) 

         {

         }

}

 

//w kontrolerze odnoszącym się do widoku zawierającym picker

var model = new DocumentPickerModel (documents);

documentPicker.Model = model;

//odczyt

var documentModel = documentPicker.Model as DocumentPickerModel;

//documentModel.SelectedItem

//zdarzenie wyboru

model.ItemSelected += (object sender, EventArgs e) => {

}

Programowe ukrywanie klawiatury

var tap = new UITapGestureRecognizer();

tap.AddTarget(() => {

        View.EndEditing(true);

});

View.AddGestureRecognizer(tap);

RestSharp - prosty klient Http w Xamarin Components

Windows 10 - najnowsze newsy

Witam, tym razem kolejny miks newsów o Windows 10. Windows 10 na telefony, phablety i wszystko co ma mniejszą niż 8 cali przekątną ekranu nie będzie udostępniało aplikacji desktopowych i pulpitu. Czyli można powiedzieć, że wciąż będzie wersja systemu obsługująca tylko aplikacje Modern UI oraz wersja obsługująca Modern UI i aplikacje desktop. Plotka o nowym procesorze Intela w HoloLens. Podczas prezentacji pokazano w Windows 10 przezroczyste menu oraz przycisk Back w górnym pasku aplikacji Modern. Funkcjonalności te nie trafiły do publicznie udostępnionego buildu. Trwają pracę nad systemem wtyczek do nowej przeglądarki Spartan. Po instalacji jednej z aktualizacji można było ściągnąć nowsze buildy Windows 10, których nie dało się uruchomić z uwagi na to, że nie były do publicznego użytku. Nowe dialogi do umieszczania aplikacji Modern UI na pulpicie.  Windows 10 for Phone będzie wspierać natywnie multimedialny format FLAC.

sobota, 24 stycznia 2015

Kulisy Xamarina - odc. 1: podstawy

Jako długoletni praktyk związany z C# i .NET, pasjonat najnowszych platform Windows, jak również osoba, której przestały być obce platformy Android i iOS, pozwolę sobie otworzyć nowy wątek tematyczny poświęcony aplikacjom Android i iOS pisanym przy użyciu C#. Zdaję sobie sprawę, że wsadzam kij w mrowisko, ale myślę, że mogę obiektywnie na to spojrzeć z różnych stron. Brakuje mi poważnej i merytorycznej dyskusji na ten temat. Ile C#/.NET, a ile SDK Android czy iOS  możemy tutaj użyć? Jakie są zyski, jakie straty? Jak C# wykonuje się na konkurencyjnych platformach i jak może współpracować z zupełnie odmiennymi środowiskami? Czy trzeba mieć Mac-a? Co z emulatorami?  Odpowiedzi m.in na takie pytania zacząłem szukać jakiś tydzień temu mając pod ręką najnowszy Visual Studio 2015 CTP z zainstalowanym Xamarin (w bardziej funkcjonalnej wersji Business dostępnej za darmo przez 30 dni), ale po drodze wyszedł Windows 10 i trochę się to odłożyło w czasie.

Ale przejdźmy do sedna. Na Androidzie mamy doczynienia z dualizmem enginów i platform: Mono rozmawia z wrapperami obiektów Javy z engina Dalvik, a ten ostatni komunikuje się z Mono. Wynika to z liberalizmu Android co do silników pisanych pod niego aplikacji. Na iOS takiej dowolności nie ma, engine Apple jest jedynym słusznym enginem (podobnie jak WinRT dla aplikacji mobilnych na Windows, choć tam panuje większy liberalizm w zakresie stosowanych technologii i języków). Ale i z tym Xamarin sobie poradził, oryginalnymi narzędziami Apple kompiluje do postaci natywnej nie tylko kod naszej aplikacji w C#, ale i dostarczane przez siebie API w tym języku.

Chociaż Xamarin ma swój edytor, to użytkownicy wersji Business z pewnością będą używali Visual Studio. W takiej konfiguracji aplikacje tworzymy jako projekty w Visual Studio, który obecnie ma designery do plików definiujących UI w Android i iOS. W przypadku Android nawiązywana jest łączność z narzędziami z oryginalnego SDK (łączy się też z nimi Android Studio i Eclipse), aplikację możemy uruchamiać/debugować na emulatorach z SDK, emulatorach Android dostarczanych przez Visual Studio 2015 (są wydajniejsze, używają Hyper-V, póki co w tej rozwojowej wersji VS nie udało mi się użyć ich z Xamarinem) i fizycznych urządzeniach. W przypadku iOS Xamarin nie zwalnia nas od posiadania maszyny z systemem OS X, ponieważ używa oryginalnego SDK do kompilowania, debugowania/uruchamiania na emulatorach czy urządzeniach. Aby z tego korzystać, wcześniej na maszynie z systemem Apple’a instalujemy oryginalne narzędzia deweloperskie (iOS SDK / XCode) i Xamarina, po czym dokonujemy tzw. parowania uruchomionego tam hosta z Visual Studio.

Xamarin wydaje się dobrym rozwiązaniem dla deweloperów .NET otwartych na inne platformy, ponieważ z jednej strony dostarcza większą wygodę tworzenia poprzez C# i Visual Studio, a z drugiej strony używa oryginalnych narzędzi z  Android SDK czy iOS. Przy czym nie ma tutaj cudów. Kto zna platformy Android czy iOS, ten szybko odnajdzie się  i doceni odpowiedniki znanych mu funkcjonalności w C#.  Kto ich nie zna, to musi je w jakimś stopniu poznać , sama znajomość C# na pewno nie jest wystarczająca. Można by tutaj postawić pytanie, że jeśli ktoś zna platformy Android czy iOS w oryginalnych podejściach, to po co mu jeszcze eksperymenty z C#?  Oryginalne narzędzia czy języki nie są jakoś specjalnie bardziej trudne (Objective-C ucywilizował się, nie wymaga obecnie zarządzania referencjami, poza tym mamy od niedawna bardzo nowoczesny Swift), może czasami są tylko trochę mniej wygodne w porównaniu do C# (brak pewnych konstrukcji w innych językach np. zdarzeń w Javie czy generyków w Objective-C; mniej wygodne lub inaczej działające narzędzia). Tak, to prawda, ale jest jedna rzecz, którą daje nam Xamarin: możliwość współdzielenia kodu C# pomiędzy Android, iOS i Windows. Jeśli mamy napisać aplikację na trzy platformy, to może warto to rozważyć?  Nie chodzi nawet o oszczędność czasu na napisanie, czy późniejsze utrzymanie ekosystemu. Warto tutaj podkreślić, że współdzielenie kodu w Xamarin nie jest robione jak w Web, gdzie interfejs użytkownika tworzony jest w zupełnym oderwaniu od natywnych komponentów. Xamarin pozwala nam samym decydować, co współdzielimy, a czego nie, pozwala bezpośrednio korzystać z natywnych interfejsów użytkownika (stosunkowo od niedawna oferuje też framework z uniwersalnym XAML mapującym się na natywne elementy poszczególnych platform) oraz nie ogranicza w korzystaniu z natywnego API (obiecują pełne pokrycie i zgodność z wydawanymi na bieżąco wersjami platform, przyjrzymy się temu). Niewątpliwym minusem jest fakt, że za bardziej funkcjonalne wersje musimy zapłacić.

Poniżej, jak zawsze bardziej szczegółowe zapiski.

 

Intro

Xamarin.Android

  • historia:  Mono for Android –> Xamarin.Android
  • platforma
    • Android Kernel  -> Dalvik Runtime –> android.* (np. aktywności) + java.*
    • Android Kernel -> Mono Runtime (+ ACW - Android Called Wrapper, odwołania Dalvik do mono)  –> .NET API + Android Bindings (+MCW - managed called wrappers: wrappery na android.*)
  • Visual Studio: edytor kodu, UI designer (aktywności), kompilacja, debugowanie (możliwa integracja z ADB Server)

Xamarin.iOS

  • historia:  MonoTouch  -> Xamarin.iOS
  • platforma:  iOS (inny runtime niż z Apple nie jest dozwolony) –> [ .NET API + iOS Bindings  + kod aplikacji ] => kompilacja do pełnej aplikacji
  • Visual Studio: edytor kodu, designer UI zgodny z UIKit, obsługuje storyboards!  (dawniej trzeba było za pomocą Xamarin Studio na OS X wygenerować kod C# dla UI), współpraca z Xamarin.iOS.Server na OS X - zdalna kompilacja i debugowanie

Współdzielenie kodu

  • logika biznesowa
  • dostęp do danych / zarządzanie
  • dostęp do serwisów
  • portable class library

Xamarin.Forms

  • uniwersalnie definowane w C# UI dla iOS, Android, Windows Phone z zachowaniem pełnej natywności
  • możliwość definiowania XAML z data bindingami !  (XAML nie jest kompatybilny z dotychczasowymi edytorami VS)
  • predefiniowane rodzaje stron, layouty i kontrolki
  • wsparcie dla własnych kontrolek
  • możliwość stosowania MVVM
  • animacje (podstawowe i niskopoziomowe, wszystkie operacje odwołują się do natywnego API)

 

Xamarin.Android

[Activity(Label = “XXX”, MainLauncher = true, Icon = “@drawable/icon”]

public class MyActivity: Activity

{  

          Button buttonOk;

          ImageView  imageProduct;     

           protected override void OnCreate(Bundle bundle)

           {

                    base.OnCreate(bundle);

 

                    SetContentView(Resource.Layout.Main);

 

                    buttonOk = FindViewById<Button>(Resource.Id.buttonOk);   // @+id/buttonOk

                    imageProduct = FindViewById<ImageView>(Resource.Id.imageProduct);  

 

                    buttonOk.Click += buttonOk_Click;

           }

           void buttonOk_Click(object sender, EventArgs e)

           {

                     imageProduct.SetImageResource(Resource.Drawable.image_01);

           }

}

layout: plik .axml  (Android)

+ alternatywne wersje metod z generykami

+ typowa dla .NET obsługa zdarzeń i propercji

- nie tak rozbudowane kreatory jak w Android Studio

- potrzeba rekompilacji do przegenerowania klas z zasobami

integracja z narzędziami Android SDK

atrybuty zastępują część wartości podawanych w manifeście Android

Udało się uruchomić i debugować aplikację na emulatorze dla Cordovy (tym z SDK). Niestety w obecnej wersji jest jakiś nieznany błąd przy próbie uruchomienia na emulatorze Visual Studio (tym na Hyper-V).

 

Xamarin.iOS

OS X:

  • standardowe narzędzia deweloperskie dla iOS
    • XCode
    • iOS SDK
    • Symulator
  • Xamarin.iOS for OS X (po zalogowaniu pobieramy ze strony Xamarin)
    • Xamarin Studio (generacja kodu UI)
    • Xamarin.iOS Server (zarządzanie kompilacją i debugowaniem, uruchamianie symulatora, podłączenie do urządzenia)
    • Xamarin.iOS Build Host (na liście aplikacji): Pair - parowanie z maszyną z Visual Studio, generuje PIN, który należy podać w Visual Studio podczas kompilacji; po konfiguracji VS na pasku zadań: Options –> Keep in Dock

Windows:

  • Visual Studio
    • Tools –> Options –> Xamarin –> iOS Settings –> Find Mac Build Host (wybieramy i Connect, podajemy PIN, Pair)
    • panel Output –> Mac Server Log
  • Xamarin.iOS
  • Xamarin Bonjour Service (wykrywanie serwisów na OS X, delegacja do Xamarin.iOS Server)

Możemy pisać sobie duże ilości C#…

   public partial class RootViewController: UIViewController
    {

        UIButton button;
        float buttonWidth = 200;
        float buttonHeight = 50;

        
        public RootViewController(IntPtr handle) : base(handle)
        {
        }

        …

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            View.Frame = UIScreen.MainScreen.Bounds;
            View.BackgroundColor = UIColor.Yellow;
            View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;

            button = UIButton.FromType(UIButtonType.System);

            button.Frame = new RectangleF(
                (float)View.Frame.Width / 2 - buttonWidth / 2,
                (float)View.Frame.Height / 2 - buttonHeight / 2,
                buttonWidth,
                buttonHeight
                );      

            button.SetTitle("OK", UIControlState.Normal);

            button.TouchUpInside += (s, e) =>
            {
                button.SetTitle("XXX", UIControlState.Normal);
            };

            Add(button);

        }

        …

     }

Ale obecnie mamy designer dla storyboard w samym Visual Studio !

image

Umieszczam przycisk. Definiuję sobie warunki na layout (ideowo podobnie jak w XCode daję sobie centrowanie). Ustawiam kolor tła. Klikam przycisk dwukrotnie i mam już podpięty handler. Klasa C# widoku składa się z dwóch części partial. W części designera mamy kod:

    [Register ("RootViewController")]
    partial class RootViewController
    {
        [Action ("UIButton21_TouchUpInside:")]
        [GeneratedCode ("iOS Designer", "1.0")]
        partial void UIButton21_TouchUpInside (UIButton sender);

        void ReleaseDesignerOutlets ()
        {
        }
    }

W części “naszej” wygląda to tak:

    public partial class RootViewController: UIViewController
    {              
        public RootViewController(IntPtr handle) : base(handle)
        {
        }

        …

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

        }

        …

        partial void UIButton21_TouchUpInside(UIButton sender)
        {
            sender.SetTitle("AAA", UIControlState.Normal);
        }

     }

We wcześniejszych wersjach mogliśmy tylko w Xamarin Studio w OS X edytować natywny plik UI (w otwartym solution z VS). Edycja może się odbyć za pomocą XCode (w starszych wersjach jedyna możliwość), w którym robi się to co zwykle w designerze, definiujemy także outlety i akcje poprzez przeciąganie do interfejsu/implementacji kontrolera w Objective-C. Po zapisaniu i zamknięciu okna XCode, Xamarin Studio w partial widoku dla designera tłumaczy kod Objective-C na generowany przez siebie kod C# (można też samemu uruchamiać sychronizację z XCode przez polecenie Tools –> Sync with XCode).

Po uruchomieniu debugujemy:

xamarin.ios

Dodawanie obrazków do projektu Visual Studio:

  • Build Action ma być ustawiony na BundleResource
  • Ustawienie obrazka w ImageView

imageView.Image = UIImage.FromBundle(“image_01”);

Współdzielenie kodu między różnymi platformami

  • Portable Class Library
  • zalecany MVVM
  • w przypadku kompilacji pod iOS  w Configuration Manager aktywną platformę dla solution jest iPhoneSimulator

Międzyplatformowe zarządzanie obrazkami

  • iOS - prosto: nazwa obrazka z dzielonej biblioteki, reszta jak wyżej
  • Android - definiujemy własną klasę mapującą stringi z dzielonej biblioteki na stałe w Resource.Drawable (wyrażenie switch lub refleksja - dla większej wydajności raz odczytane wartości można cachować w słowniku)