Xamarin.Forms Android custom ContentPage PageRenderer not rendering views - xamarin.android

This doc explains how to create custom PageRenderer for Android, iOS etc. I tried as per docs but don't know why it's not working for Android. However it does works for iOS.
Shared ContentPage class:
public class SecondPage : ContentPage
{
public SecondPage()
{
Content = new StackLayout
{
Children = {
new Label { Text = "Hello ContentPage" }
}
};
}
}
Custom PageRenderer class for Android:
[assembly: ExportRenderer(typeof(SecondPage), typeof(SecondPageRenderer))]
namespace RenderFormsTest.Droid
{
public class SecondPageRenderer : PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
return;
}
InitView();
}
void InitView()
{
var context = Forms.Context;
string[] os = { "Android", "iOS", "Windows" };
var ll = new LinearLayout(context);
ll.LayoutParameters = new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent);
ll.SetBackgroundColor(Android.Graphics.Color.IndianRed);
var rg = new RadioGroup(context);
for (int index = 0; index < os.Length; index++)
{
rg.AddView(new RadioButton(context) { Text = os[index] });
}
ll.AddView(rg);
AddView(ll);
}
}
}
Can you please tell me what went wrong ?

I had similar problems. It seems the layout is incorrect. Try to set it in PageRenderer.OnLayout:
[assembly: ExportRenderer(typeof(SecondPage), typeof(SecondPageRenderer))]
namespace RenderFormsTest.Droid
{
protected Android.Views.View NativeView;
public class SecondPageRenderer : PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
if (Element == null)
NativeView = null;
return;
}
InitView();
}
void InitView()
{
var context = Forms.Context;
string[] os = { "Android", "iOS", "Windows" };
var ll = new LinearLayout(context);
ll.LayoutParameters = new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent);
ll.SetBackgroundColor(Android.Graphics.Color.IndianRed);
var rg = new RadioGroup(context);
for (int index = 0; index < os.Length; index++)
{
rg.AddView(new RadioButton(context) { Text = os[index] });
}
ll.AddView(rg);
AddView(ll);
NativeView = ll;
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
if ( NativeView != null )
{
var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly);
var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly);
NativeView.Measure(msw, msh);
NativeView.Layout(0, 0, r - l, b - t);
}
}
}
}

Related

MpAndroidChart for Xamarin.android

I'm using MpAndroidChart for my app. I can create piechart successfully but I have a question. I want an event listener to handle item clicked when user touched one of piechart item.How can I do it?
My Code is as below;
>
int[] ydata = { 5, 2, 3, 1 };
string[] xdata= { "one","two","three","four" };
PieChart pieChart;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
pieChart = FindViewById<PieChart>(Resource.Id.chart1);
pieChart.RotationEnabled = true;
pieChart.HoleRadius = 0;
pieChart.SetTransparentCircleAlpha(0);
pieChart.SetCenterTextSize(20);
pieChart.SetDrawEntryLabels(true);
pieChart.SetUsePercentValues(false);
pieChart.AnimateX(1000, Easing.EasingOption.EaseInOutCubic);
pieChart.Description.Text = "test";
addDataSet();
pieChart.SetTouchEnabled(true);
}
private void addDataSet()
{
List<PieEntry> yEntry = new List<PieEntry>();
List<string> xEntry = new List<string>();
for (int i = 0; i < ydata.Length; i++)
{
yEntry.Add(new PieEntry(ydata[i],xdata[i]));
}
for (int i = 0; i < xdata.Length; i++)
{
xEntry.Add(xdata[i]);
}
PieDataSet piedataset = new PieDataSet(yEntry, "test");
piedataset.SliceSpace = 0;
piedataset.ValueTextSize = 20;
int[] colors= { Color.Blue,Color.Red,Color.Green,Color.White};
piedataset.SetColors(colors);
Legend legend = pieChart.Legend;
legend.Form=Legend.LegendForm.Circle;
PieData pieData = new PieData(piedataset);
//pieData.SetValueFormatter(new int);
pieData.SetValueTextColor(Color.White);
pieChart.Data = (pieData);
pieChart.Invalidate();
}
You could use SetOnChartValueSelectedListener to set a ChartValueSelectedListener.
For example, in onCreate():
pieChart.SetOnChartValueSelectedListener(new MyListener(this));
And the listener:
public class MyListener : Java.Lang.Object, IOnChartValueSelectedListenerSupport
{
Context mContext;
public MyListener(Context context) {
this.mContext = context;
}
public void OnNothingSelected()
{
// throw new NotImplementedException();
}
public void OnValueSelected(Entry e, Highlight h)
{
PieEntry pe = (PieEntry)e;
Toast.MakeText(mContext, pe.Label+ " is clicked, value is "+ pe.Value, ToastLength.Short).Show();
}
}
The result is:

Listview Filter with SearchView Using Base Adapter in Xamarin android Error

I am try to filter listview with searchview using Base Adapter in in xamarin Android, My listView Bind in sql server using restfull web service i am stuck in PublishResults which is given an error
Here Is My Code:-
GetHospNames.cs
public class GetHospNames
{
public string HospID { get; set; }
public string HospName { get; set; }
public GetHospNames(string HospID, string HospName)
{
this.HospID = HospID;
this.HospName = HospName;
//this.HospLogo = HospLogo;
}
}
ContListViewHospNameClass.cs
using System.Collections.Generic;
using Android.App;
using Android.Views;
using Android.Widget;
using System;
using Android.Graphics;
using Android.Graphics.Drawables;
using System.IO;
using Android.Content;
using Java.Lang;
using Android.Text;
using Java.Util;
using Oject = Java.Lang.Object;
namespace HSAPP
{
public class ContListViewHospNameClass : BaseAdapter<GetHospNames>, IFilterable
{
public List<GetHospNames> objList;
Activity objActivity;
List<GetHospNames> filterList;
public ContListViewHospNameClass(Activity objMyAct, List<GetHospNames> objMyList) : base()
{
this.objActivity = objMyAct;
objList = objMyList;
this.filterList = objList;
Filter = new CustomFilter(this);
}
public override GetHospNames this[int position]
{
get
{
return objList[position];
}
}
public override int Count
{
get
{
return objList.Count;
}
}
public Filter Filter { get; set; }
public override void NotifyDataSetChanged()
{
base.NotifyDataSetChanged();
}
//This is Inner Class
public class CustomFilter : Filter
{
ContListViewHospNameClass CustomAdapter;
public CustomFilter(ContListViewHospNameClass adapter) : base()
{
this.CustomAdapter = adapter;
}
protected override FilterResults PerformFiltering(ICharSequence constraint)
{
FilterResults result = new FilterResults();
if (constraint != null && constraint.Length() > 0)
{
//Contraint To Upper
List<GetHospNames> filter = new List<GetHospNames>();
foreach (GetHospNames name in CustomAdapter.objList)
{
if (name.HospName.ToUpper().Contains(constraint.ToString().ToUpper()))
{
filter.Add(name);
}
}
Oject[] Name;
Name = new Oject[filter.Count];
for (int i = 0; i < filter.Count; i++)
{
Name[i] = filter[i].HospName.ToString();
}
result.Count = filter.Count;
result.Values = Name;
}
return result;
}
protected override void PublishResults(ICharSequence constraint, Filter.FilterResults result)
{
List<GetHospNames> filteredList = new List<GetHospNames>();
for (int i = 0; i < ((Oject[])result.Values).Length; i++)
{
filteredList.Add((Oject[])result.Values[i]);//Here Is An Error *****Cannot apply indexing with [] to an expression of type 'Object'****
}
CustomAdapter.objList = filteredList;
CustomAdapter.NotifyDataSetChanged();
}
}
public override long GetItemId(int position)
{
return position;
}
public Bitmap getBitmap(byte[] getByte)
{
if (getByte.Length != 0)
{
return BitmapFactory.DecodeByteArray(getByte, 0, getByte.Length);
}
else
{
return null;
}
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = objList[position];
if (convertView == null)
{
convertView = objActivity.LayoutInflater.Inflate(Resource.Layout.ContListViewHospName, null);
}
convertView.FindViewById<TextView>(Resource.Id.tvHospID).Text = item.HospID;
convertView.FindViewById<TextView>(Resource.Id.tvHospName).Text = item.HospName;
return convertView;
}
}
public static class ObjectTypeHelper
{
public static T Cast<T>(this Java.Lang.Object obj) where T : class
{
var propertyInfo = obj.GetType().GetProperty("Instance");
return propertyInfo == null ? null : propertyInfo.GetValue(obj, null) as T;
}
}
}
This is my MainActivity Code
private void BindControl_BindHospCompleted(object sender, BindControl.BindHospCompletedEventArgs e)
{
jsonValue = e.Result.ToString();
try
{
if (jsonValue == null)
{
Toast.MakeText(this, "No Data For Bind", ToastLength.Long).Show();
return;
}
JArrayValue = JArray.Parse(jsonValue);
list = new List<GetHospNames>();
int count = 0;
while (count < JArrayValue.Count)
{
GetHospNames getHospName = new GetHospNames(JArrayValue[count]["HospID"].ToString(), JArrayValue[count]["HospName"].ToString());
list.Add(getHospName);
count++;
}
if (count == 0)
{
Toast.MakeText(this, "No List Of Hospitals", ToastLength.Long).Show();
}
adapter = new ContListViewHospNameClass(this, list);
listView.Adapter = adapter;
search.QueryTextChange += (s, e) =>
{
adapter.Filter.InvokeFilter(e.NewText);
};
listView.ItemClick += ListView_ItemClick;
pBar.Dismiss();
}
catch (Java.Lang.Exception ex)
{
pBar.Dismiss();
//Toast.MakeText(this, ex.ToString(), ToastLength.Long).Show();
Finish();
Intent intent = new Intent(this, typeof(ChkIntConnActivity));
StartActivity(intent);
}
}
Please Help...Thank You

Xamarin Forms iOS CustomRenderer not repainting

I have a custom renderer to display HTML formatted text in a UITextView. If I hard-code the text into the constructor of the page that contains the control (so it gets set in the custom control's OnElementChanged event), it displays fine. If I await a api call to get the text and then set it (so it gets set in the custom control's OnElementPropertyChanged event) it does not repaint. If I change the orientation of the device, the text appears. What do I need to add to get it to display the text when it is set?
[assembly: ExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
namespace MyApp.iOS.Renderers
{
class HtmlLabelRenderer : ViewRenderer<HtmlLabel, UITextView>
{
private UITextView _htmlTextView = new UITextView();
protected override void OnElementChanged(ElementChangedEventArgs<HtmlLabel> e)
{
base.OnElementChanged(e);
if (Element?.Text == null) return;
SetHtmlText(Element.Text);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (string.Equals(e.PropertyName, "Text", StringComparison.CurrentCultureIgnoreCase))
{
SetHtmlText(((HtmlLabel)sender).Text);
_htmlTextView.SetNeedsDisplay();
}
base.OnElementPropertyChanged(sender, e);
}
private void SetHtmlText(string text)
{
var attr = new NSAttributedStringDocumentAttributes {DocumentType = NSDocumentType.HTML};
var nsError = new NSError();
_htmlTextView.Editable = false;
_htmlTextView.AttributedText = new NSAttributedString(text, attr, ref nsError);
_htmlTextView.DataDetectorTypes = UIDataDetectorType.All;
SetNativeControl(_htmlTextView);
}
}
}
Update : I got further by changing the OnElementChanged to:
protected override void OnElementChanged(ElementChangedEventArgs<HtmlLabel> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null) return;
SetHtmlText(e.NewElement.Text ?? string.Empty);
SetNativeControl(_htmlTextView);
}
now if I have more than one HtmlLabel on the page all except the first one displays.
Try changing from:
_htmlTextView.SetNeedsDisplay();
to
SetNeedsDisplay();
or,
(Control ?? NativeView).SetNeedsDisplay();
Sharada Gururaj is right, changing to derive from Editor worked for iOS .. but breaks Android. Though this seems brute force, I used conditional compilation to get it working...
namespace MyApp.Renderers
{
#if __IOS__
public class HtmlLabel : Editor
{
}
#else
public class HtmlLabel : Label
{
}
#endif
}
Here is the android
[assembly: ExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
namespace MyApp.Droid.Renderers
{
public class HtmlLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (HtmlLabel)Element;
if (view?.Text == null) return;
SetHtmlText(view.Text);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Label.TextProperty.PropertyName)
{
SetHtmlText(((HtmlLabel) sender).Text);
}
}
private void SetHtmlText(string text)
{
var encodedText = (((int)Build.VERSION.SdkInt) >= 24) ? Html.FromHtml(text, FromHtmlOptions.ModeLegacy) :
#pragma warning disable 618
// need this for backward compatability
Html.FromHtml(text);
#pragma warning restore 618
Control.MovementMethod = LinkMovementMethod.Instance;
Control.SetText(encodedText, TextView.BufferType.Spannable);
}
}
}
Your custom HtmlLabel class should be able to derive from the same thing on Android and iOS
namespace YourNameSpace
{
public class HtmlLabel : Label
{
}
}
The renderer for Android should look something like this
[assembly: ExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
namespace YourNameSpace.Droid
{
public class HtmlLabelRenderer : ViewRenderer<Label, TextView>
{
TextView _textView;
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
if(Control == null)
{
_textView = new TextView(Context);
SetHtmlText(Element.Text);
SetNativeControl(_textView);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Element == null || Control == null)
return;
if (e.PropertyName == HtmlLabel.TextProperty.PropertyName)
{
SetHtmlText(Element.Text);
}
}
private void SetHtmlText(string text)
{
if (Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.N)
{
_textView.TextFormatted = Html.FromHtml(text, Android.Text.FromHtmlOptions.ModeCompact);
}
else
{
_textView.TextFormatted = Html.FromHtml(text);
}
}
}
}
And on iOS it should look very similar
[assembly: ExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
namespace YourNameSpace.iOS
{
public class HtmlLabelRenderer : ViewRenderer<Label, UITextView>
{
UITextView _textView;
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
if(Control == null)
{
_textView = new UITextView();
SetHtmlText(Element.Text);
SetNativeControl(_textView);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Element == null || Control == null)
return;
if (e.PropertyName == HtmlLabel.TextProperty.PropertyName)
{
SetHtmlText(Element.Text);
}
}
private void SetHtmlText(string text)
{
var attr = new NSAttributedStringDocumentAttributes { DocumentType = NSDocumentType.HTML };
var nsError = new NSError();
_textView.Editable = false;
_textView.AttributedText = new NSAttributedString(text, attr, ref nsError);
_textView.DataDetectorTypes = UIDataDetectorType.All;
}
}
}
I tested that on Android and it worked, calling OnElementPropertyChanged when the text changed and everything. However, I don't have a mac at home to try the iOS Renderer so I'm just assuming it will function pretty much the same.

Make two ListBoxes scroll together

I wish to make two ListBoxes scroll together.
I have two ListBoxes of the same height with the same number of items, etc. I want to set it up such that if the user scrolls up/down in one list box the scrollbar for the other ListBox scrolls up/down as well.
But I can not seem to find a way to either detect the scroll bar position value or to detect when it has changed value.
Here is another way to sync the two ListBoxes:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace SyncTwoListBox
{
public partial class Form1 : Form
{
private SyncListBoxes _SyncListBoxes = null;
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
//add options
for (int i = 0; i < 40; i++)
{
listBox1.Items.Add("Item " + i);
listBox2.Items.Add("Item " + i);
}
}
private void Form1_Load(object sender, EventArgs e)
{
this._SyncListBoxes = new SyncListBoxes(this.listBox1, this.listBox2);
}
}
public class SyncListBoxes
{
private ListBox _LB1 = null;
private ListBox _LB2 = null;
private ListBoxScroll _ListBoxScroll1 = null;
private ListBoxScroll _ListBoxScroll2 = null;
public SyncListBoxes(ListBox LB1, ListBox LB2)
{
if (LB1 != null && LB1.IsHandleCreated && LB2 != null && LB2.IsHandleCreated &&
LB1.Items.Count == LB2.Items.Count && LB1.Height == LB2.Height)
{
this._LB1 = LB1;
this._ListBoxScroll1 = new ListBoxScroll(LB1);
this._ListBoxScroll1.Scroll += _ListBoxScroll1_VerticalScroll;
this._LB2 = LB2;
this._ListBoxScroll2 = new ListBoxScroll(LB2);
this._ListBoxScroll2.Scroll += _ListBoxScroll2_VerticalScroll;
this._LB1.SelectedIndexChanged += _LB1_SelectedIndexChanged;
this._LB2.SelectedIndexChanged += _LB2_SelectedIndexChanged;
}
}
private void _LB1_SelectedIndexChanged(object sender, EventArgs e)
{
if (this._LB2.TopIndex != this._LB1.TopIndex)
{
this._LB2.TopIndex = this._LB1.TopIndex;
}
if (this._LB2.SelectedIndex != this._LB1.SelectedIndex)
{
this._LB2.SelectedIndex = this._LB1.SelectedIndex;
}
}
private void _LB2_SelectedIndexChanged(object sender, EventArgs e)
{
if (this._LB1.TopIndex != this._LB2.TopIndex)
{
this._LB1.TopIndex = this._LB2.TopIndex;
}
if (this._LB1.SelectedIndex != this._LB2.SelectedIndex)
{
this._LB1.SelectedIndex = this._LB2.SelectedIndex;
}
}
private void _ListBoxScroll1_VerticalScroll(ListBox LB)
{
if (this._LB2.TopIndex != this._LB1.TopIndex)
{
this._LB2.TopIndex = this._LB1.TopIndex;
}
}
private void _ListBoxScroll2_VerticalScroll(ListBox LB)
{
if (this._LB1.TopIndex != this._LB2.TopIndex)
{
this._LB1.TopIndex = this._LB2.TopIndex;
}
}
private class ListBoxScroll : NativeWindow
{
private ListBox _LB = null;
private const int WM_VSCROLL = 0x115;
private const int WM_MOUSEWHEEL = 0x20a;
public event dlgListBoxScroll Scroll;
public delegate void dlgListBoxScroll(ListBox LB);
public ListBoxScroll(ListBox LB)
{
this._LB = LB;
this.AssignHandle(LB.Handle);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case WM_VSCROLL:
case WM_MOUSEWHEEL:
if (this.Scroll != null)
{
this.Scroll(_LB);
}
break;
}
}
}
}
}
enter image description here

Create a custom Spinner

I am trying to customize the MvxSpinner to add some additional controls, here is my code:
public class ChamSpinner : LinearLayout
{
public Spinner Spinner{ get; private set; }
public EventHandler<AdapterView.ItemSelectedEventArgs> ItemSelected;
public ChamSpinner (Context context, IAttributeSet attrs) : this (context, attrs, new ChamSpinnerAdapter (context))
{
}
public ChamSpinner (Context context, IAttributeSet attrs, IMvxAdapter adapter) : base (context, attrs)
{
((Activity)Context).LayoutInflater.Inflate (Resource.Layout.ChamSpinnerLayout, this);
Spinner = FindViewById<Spinner> (Resource.Id.ChamSpinnerSpinner);
int itemTemplateId = MvxAttributeHelpers.ReadListItemTemplateId (context, attrs);
int dropDownItemTemplateId = MvxAttributeHelpers.ReadDropDownListItemTemplateId (context, attrs);
adapter.ItemTemplateId = itemTemplateId;
adapter.DropDownItemTemplateId = dropDownItemTemplateId;
Adapter = adapter;
SetupHandleItemSelected ();
}
public new IMvxAdapter Adapter
{
get { return Spinner.Adapter as IMvxAdapter; }
set
{
var existing = Adapter;
if (existing == value)
return;
if (existing != null && value != null)
{
value.ItemsSource = existing.ItemsSource;
value.ItemTemplateId = existing.ItemTemplateId;
}
Spinner.Adapter = value;
}
}
[MvxSetToNullAfterBinding]
public IEnumerable ItemsSource
{
get
{
return Adapter.ItemsSource;
}
set
{
Adapter.ItemsSource = value;
}
}
public int ItemTemplateId
{
get { return Adapter.ItemTemplateId; }
set { Adapter.ItemTemplateId = value; }
}
public int DropDownItemTemplateId
{
get { return Adapter.DropDownItemTemplateId; }
set { Adapter.DropDownItemTemplateId = value; }
}
public ICommand HandleItemSelected { get; set; }
private void SetupHandleItemSelected ()
{
Spinner.ItemSelected += (sender, args) =>
{
var position = args.Position;
HandleSelected (position);
if (ItemSelected != null)
ItemSelected (sender, args);
};
}
protected virtual void HandleSelected (int position)
{
var item = Adapter.GetRawItem (position);
if (this.HandleItemSelected == null
|| item == null
|| !this.HandleItemSelected.CanExecute (item))
return;
this.HandleItemSelected.Execute (item);
}
}
And I am using it like this:
<cross.android.ChamSpinner
android:layout_width="fill_parent"
android:layout_height="wrap_content"
local:MvxDropDownItemTemplate="#layout/myspinneritemdropdown"
local:MvxItemTemplate="#layout/myspinneritem"
local:MvxBind="ItemsSource MyItemsSource; SelectedItem MyItem; Mode TwoWay" />
The spinner is always empty, I tried to add a custom binding on ItemsSource property but the result stilll the same.
How can I do to show my items in my spinner?
Thank you in advance.
I think using BindingInflate instead of Inflate should fix it or even points you in the right direction. https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Droid/BindingContext/IMvxAndroidBindingContext.cs
((IMvxBindingContextOwner)Context).BindingInflate(Resource.Layout.ChamSpinnerLayout, this);
I found this error in my log
MvxBind:Error: 32,12 View type not found - cross.android.ChamSpinner
My custom control is in another assembly so I added it to MvvmCross View assemblies is my Setup class like this
protected override IList<Assembly> AndroidViewAssemblies
{
get
{
var assemblies = base.AndroidViewAssemblies;
assemblies.Add(typeof(ChamSpinner).Assembly);
return assemblies;
}
}
Thank you Stuart for your advices and for your great Framework.

Resources