András Tóth‘s professional blog
banditoth.net

Hey there 👋, I’m banditoth a .NET MAUI developer from Hungary.
I write about software development with .NET technologies.

You can find me on:
LinkedIn | Github | StackOverflow | X / Twitter | Threads

  • Xamarin Forms: Custom Rendererek más assemblyből

    This content has 5 years. Some of the information in this post may be out of date or no longer work. Please, read this page keeping its age in your mind.

    A Xamarin Forms a CustomRenderer Attribútumot csak a betöltött referenciák között keresi. Ez azt jelenti, hogyha egy másik projektben található a customrenderer, akkor annak a projektnek kifordított dll-jének már a Forms.Init előtt be kell töltődnie. Jó megoldás lehet, ha létrehozunk egy statikus osztályt, aminek a statikus Initalize metódusára ráhívunk Android esetén az onCreate, iOS esetén pedig az AppDelegate FinishedLauching függvényében.

    public static class Initainer
    {
        public static void Initalize()
        {
            Console.WriteLine("Initainer is initializing Custom renderers")
        }
    }
    
  • Xamarin Forms: Auto magasságú ListView

    This content has 5 years. Some of the information in this post may be out of date or no longer work. Please, read this page keeping its age in your mind.

    Formsban, ha egy ListView HorizontalOptions, VerticalOptions tulajdonságait Fill-re állítjuk, és a ListView egy Grid sorában van mondjuk, amelynek a Height-je Auto, a lista le fogja foglalni a Grid által kínált összes helyet. Ez azért van, mert a ListView measure-nél még nem tudja hogy milyen listaelemek milyen listaelem formátummal fognak megjelenni benne. Így elveszítjük a lehetőséget a dinamikus megjelenítésre, mert a felületünk szét fog csúszni.

    Ennek a problémakörnek megoldásaképpen készítettem egy olyan ListView-t, amely képes a listaelemek tényleges lemért magassága után újraméretezni a lista magasságát. Ez a lista, mindig a benne található elemek magassága méretűre fog nyúlni. Meg lehet még fűszerezni annyival, hogy a Separatorok magasságát is beleszámolja, illetve hogy legyen egy maximálisan engedett magassága is, én ezt nem tettem meg, de jó kiindulási alap.

     public class AutoHeightListView : ListView
        {
            public AutoHeightListView()
            {
            }
    
            protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                base.OnPropertyChanged(propertyName);
    
                // ItemsSource változásakor 
                if (propertyName == nameof(ListView.ItemsSource))
                {
                    if(ItemsSource != null)
                    {
                        // Kikérjük a ListView feltemapltezett celljeit.
                        IEnumerable<PropertyInfo> pInfos = (this as ItemsView<Cell>).GetType().GetRuntimeProperties();
                        var templatedItems = pInfos.FirstOrDefault(info => info.Name == "TemplatedItems");
    
                        if (templatedItems != null)
                        {
                            var cells = templatedItems.GetValue(this);
                            if(cells is Xamarin.Forms.ITemplatedItemsList<Xamarin.Forms.Cell> cellsCasted)
                            {
                                if(cellsCasted.Count != 0)
                                {
                                    ((ViewCell)cellsCasted.Last()).View.SizeChanged += FirstElementHeightChanged;
                                }
                            }
                        }
                    }
                }
            }
    
            double prevHeight = 0;
    
            private void FirstElementHeightChanged(object sender, EventArgs e)
            {
                SetHeightRequestByCellsHeight();
            }
    
            /// <summary>
            /// Megméri minden sor Viewcell magasságát, és az egész listview magasságát ezeknek az összegére állítja be.
            /// </summary>
            private void SetHeightRequestByCellsHeight()
            {
                double calculatedHeight = 0;
    
                // Kikérjük a ListView feltemapltezett celljeit.
                IEnumerable<PropertyInfo> pInfos = (this as ItemsView<Cell>).GetType().GetRuntimeProperties();
                var templatedItems = pInfos.FirstOrDefault(info => info.Name == "TemplatedItems");
    
                if (templatedItems != null)
                {
                    var cells = templatedItems.GetValue(this);
                    foreach (ViewCell cell in cells as Xamarin.Forms.ITemplatedItemsList<Xamarin.Forms.Cell>)
                    {
                        calculatedHeight += cell.View.Height;
                    }
                }
    
                if (calculatedHeight == prevHeight)
                    return;
    
                prevHeight = calculatedHeight;
                HeightRequest = calculatedHeight;
            }
        }
    
  • Xamarin Forms / iOS: Backspace detektálás Entryben

    This content has 5 years. Some of the information in this post may be out of date or no longer work. Please, read this page keeping its age in your mind.

    Androidtól eltérően, iOS-en az EntryRenderer nem rendelkezik overrideolható DispatchKeyEventtel. Viszont magának a UITextFieldnek van egy DeleteBackward metódusa, ami a visszatörlés esetén hívódik meg. Ahhoz, hogy ezt felüldefiniáljuk, örököltetnünk kell egy UITextField-et.

    public class UITextFieldWithExtendedFuncionallity : UITextField
    {
        public delegate void DeleteBackwardEventHandler(object sender, EventArgs e);
    
        public event DeleteBackwardEventHandler OnDeleteBackward;
    
    
        public void OnDeleteBackwardPressed()
        {
            if (OnDeleteBackward != null)
            {
                OnDeleteBackward(this, new EventArgs());
            }
        }
    
        public UITextFieldWithExtendedFuncionallity()
        {
    
        }
    
        public override void DeleteBackward()
        {
            base.DeleteBackward();
            OnDeleteBackwardPressed();
        }
    }
    

    Mivel ez egy natív Custom UserControl, ahelyett, ahogy StackOverFlow-n, és egyéb helyeken ajánlják, nem az EntryRenderer-ben fogjuk beállítani natív kontrolként. StackOverflow-on, és Xamarin Fórumon CTRL+C, CTRL-V kód működik jól, de nem tekinthető szép megoldásnak. Ezeken a helyeken azt ajánlják, hogy az EntryRenderer OnElementChanged metódusában hozzunk létre egy új példányt az általunk örököltetett natív usercontrolból, majd állítsuk be azt natív kontrolként. Ilyenkor létrejön feleslegesen egy UITextField, és mi is létrehozunk egy UITextField gyereket, és az előzőleg létrejött field feleslegesen került a memóriában lefoglalásra, feleslegesen fordított a készülék erőforrásokat a konstruktorok és egyéb inicializáló függvények és procedúrák megfuttatására.

    Emiatt, létre kell hoznunk a natív egyedi usercontrolunkhoz egy Renderert a következőképp:

    [assembly: ExportRenderer (typeof(Entry), typeof(UITextFieldWithExtendedFuncionallityRenderer))]  
    namespace DemoApp.iOS.CustomControlRenderers
    {
        public class UITextFieldWithExtendedFuncionallityRenderer : ViewRenderer<Entry, UITextFieldWithExtendedFuncionallity>
        {
            private UITextFieldWithExtendedFuncionallity nativeControl;
    
            protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
            {
                base.OnElementChanged(e);
                var model = e.NewElement;
    
                if (model == null)
                {
                    return;
                }
    
                nativeControl = new UITextFieldWithExtendedFuncionallity();
    
                SetNativeControl(nativeControl);
            }
        }
    }
    

    Majd a Xamarin Forms Custom UserControl rendererjének ősét át kell állítani EntryRendererről az imént létrehozott rendererre.

    public class NakedEntryRenderer : UITextFieldWithExtendedFuncionallityRenderer
    

    Az Forms U.C. Elementchangedjében már elérhetőek a fent definiált delegatek.

    protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
            {
                base.OnElementChanged(e);
    
                if (Control != null)
                {
                    if(Control is UITextFieldWithExtendedFuncionallity nativeControl)
                    {
                        nativeControl.OnDeleteBackward += NativeControl_OnDeleteBackward;
                    }
                }
            }
    
            private void NativeControl_OnDeleteBackward(object sender, EventArgs e)
            {
                if(Element is CustomEntry formsEntry)
                {
                    formsEntry.OnBackspacePressed();
                }
            }
    

    A Forms-os UserControl-ban található BackspacePressed event az Androidos mintára épül.

    public class CustomEntry: Entry
    {
        public delegate void BackspaceEventHandler(object sender, EventArgs e);
     
        public event BackspaceEventHandler OnBackspace;
     
        public CustomEntry() { }
     
        public void OnBackspacePressed() 
        {
            if (OnBackspace != null)
            {
                OnBackspace(null, null);
            }
        }
    }
    
  • Xamarin Forms / Android: Backspace detektálása az Entrykben

    This content has 5 years. Some of the information in this post may be out of date or no longer work. Please, read this page keeping its age in your mind.

    Ahhoz, hogy a szoftveres / hardveres billentyűzeten ütött vissza gomb érzékelését detektáljuk a billentyűzeten, szükségünk lesz egy CustomRenderer-re, ahhoz pedig egy Entry száramaztatáshoz a közös kódban:

    public class CustomEntry: Entry
    {
        public delegate void BackspaceEventHandler(object sender, EventArgs e);
    
        public event BackspaceEventHandler OnBackspace;
    
        public CustomEntry() { }
    
        public void OnBackspacePressed() 
        {
            if (OnBackspace != null)
            {
                OnBackspace(null, null);
            }
        }
    }
    

    Két CustomRenderer megoldás is van Androidon. Az egyik, amelyik magát a Renderert egy InputFilter implementációvá teszi, a másik, amely egy csak egy metódust overrideol. A különbség köztük, hogy a DispatchKeyEvent override az üres entry esetén is továbbítja az eventet, míg az inputfiilter csak akkor érzékeli a visszatörlés gombnyomást, ha volt már szöveg benne.

    DispatchKeyEvent override megoldás

    public class CustomEntryRenderer: EntryRenderer
     {
        public override bool DispatchKeyEvent(KeyEvent e)
        {
            if (e.Action == KeyEventActions.Down)
            {
                if (e.KeyCode == Keycode.Del)
                {
                    if (string.IsNullOrWhiteSpace(Control.Text))
                    {
                        var entry = (PasswordBox)Element;
                        entry.OnBackspacePressed();
                    }
                }
            }
            return base.DispatchKeyEvent(e);
        }
    
        protected override void 
        OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);
        }
    }
    

    InputFilter implementáció

    [assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
    namespace App.Droid.Renderers
    {
        public class CustomEntryRenderer: EntryRenderer, Android.Text.IInputFilter
        {
            protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
            {
                base.OnElementChanged(e);
    
                if (Control == null) 
                {
                    return;
                }
    
                Control.SetFilters(new IInputFilter[] { this });
    
            }
    
            ICharSequence IInputFilter.FilterFormatted(ICharSequence source, int start, int end, ISpanned dest, int dstart, int dend)
            {
                if (string.IsNullOrWhiteSpace(source.ToString()))
                {
                    var entry = (CustomEntry)Element;
                    entry.OnBackspacePressed();
                }
                return source;
            }
    
        }
    }
    
  • Xamarin iOS: Entry Unfocus event nem triggerelődik, ListView-el.

    This content has 5 years. Some of the information in this post may be out of date or no longer work. Please, read this page keeping its age in your mind.

    Ha egy Entry épp focus alatt van, és úgy választunk egy listaelemet a ListView-ből, az Entry Unfocus eventje nem fog triggerelődni. Ez azért van, mert a ListView a Touch eseményt ilyenkor elnyeli. Az én esetemben a megoldás ListView mögött egy háttérszínnel rendelkező grid volt, ami képes touch eventeket elkapni. A ListView InputTransparent tulajdonságának True-ra állításával ez a probléma megoldódott, mert az alatta lévő grid fókuszt kapott, és “mintha kikattintottunk volna” a gridből, az entry focusa megszűnt.

            <ListView
                x:Name="list"
                BackgroundColor="#393939"
                ItemTapped="list_ItemTapped"
    
                InputTransparent="True"
    
                ItemsSource="{Binding Path=AutoCompleteItems, Source={x:Reference UserControl}}"
                SelectedItem="{Binding Path=SelectedAddress, Source={x:Reference UserControl}}"
                SeparatorVisibility="None">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <usercontrols:AddressSelectorAutoCompleteItemDataTemplate />
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
    

    Próbáltam a ListView ItemSelectedjében unfocusolni az Entry Controlokat, azonban ez nem hozott sikert.