I am reading through the source code for Xamarin and came across these two dialogs: the TimePickerDialog
and DatePickerDialog
.
When reading through these, I see that there is an object being instantiated that I am unable to find anywhere else.
I'm wondering what the purpose of these listener implementors are as well as how to actually go about creating my own (or if I even need to make my own). My goal was to create some custom PickerDialog
s so I was checking to see how they are already implemented.
Here's the TimePickerDialog
:
using System;
using System.Collections.Generic;
using Android.Runtime;
namespace Android.App {
public partial class TimePickerDialog {
public TimePickerDialog (Android.Content.Context context, EventHandler<TimeSetEventArgs> callBack, int hourOfDay, int minute, bool is24HourView)
// ** The IOnTimeSetListenerImplementor here **
: this (context, new IOnTimeSetListenerImplementor () { Handler = callBack }, hourOfDay, minute, is24HourView) {}
public TimePickerDialog (Android.Content.Context context, int theme, EventHandler<TimeSetEventArgs> callBack, int hourOfDay, int minute, bool is24HourView)
: this (context, theme, new IOnTimeSetListenerImplementor () { Handler = callBack }, hourOfDay, minute, is24HourView) {}
}
}
Here's the DatePickerDialog
:
using System;
using System.Collections.Generic;
using Android.Runtime;
namespace Android.App {
public partial class DatePickerDialog {
public partial class DateSetEventArgs {
public DateTime Date {
get { return new DateTime (Year, Month 1, DayOfMonth); }
}
#if ANDROID_24
[Obsolete ("This parameter in DateTimePickerDialog constructor is removed in Android API, so it will vanish from this automatically generated type too.")]
public int MonthOfYear {
get { return Month; }
}
#else
public int Month {
get { return monthOfYear; }
}
#endif
}
public DatePickerDialog (Android.Content.Context context, EventHandler<DateSetEventArgs> callBack, int year, int monthOfYear, int dayOfMonth)
// ** The IOnDateSetListenerImplementor here **
: this (context, new IOnDateSetListenerImplementor () { Handler = callBack }, year, monthOfYear, dayOfMonth) {}
public DatePickerDialog (Android.Content.Context context, int theme, EventHandler<DateSetEventArgs> callBack, int year, int monthOfYear, int dayOfMonth)
: this (context, theme, new IOnDateSetListenerImplementor () { Handler = callBack }, year, monthOfYear, dayOfMonth) {}
public void UpdateDate (DateTime date)
{
UpdateDate (date.Year, date.Month - 1, date.Day);
}
}
}
Bonus question: Why are these two classes defined as partial classes? I can't find the other parts anywhere else in this repo. Perhaps I am not searching correctly, or maybe it's for future expansion?
Thanks for the help!
CodePudding user response:
I found the following when I searched through the metadata of Android.App.DatePickerDialog
(easy enough to do by Right-Clicking a class name in Visual Studio and selecting Go To Definition):
#region Assembly Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065
// C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\ReferenceAssemblies\Microsoft\Framework\MonoAndroid\v11.0\Mono.Android.dll
#endregion
#nullable enable
using Android.Content;
using Android.Runtime;
using Android.Widget;
using Java.Interop;
using System;
using System.ComponentModel;
using System.Diagnostics;
namespace Android.App
{
[Register("android/app/DatePickerDialog", DoNotGenerateAcw = true)]
public class DatePickerDialog : AlertDialog, IDialogInterfaceOnClickListener, IJavaObject, IDisposable, IJavaPeerable, DatePicker.IOnDateChangedListener
{
[Register(".ctor", "(Landroid/content/Context;)V", "", ApiSince = 24)]
public DatePickerDialog(Context context);
[Register(".ctor", "(Landroid/content/Context;I)V", "", ApiSince = 24)]
public DatePickerDialog(Context context, int themeResId);
public DatePickerDialog(Context context, EventHandler<DateSetEventArgs> callBack, int year, int monthOfYear, int dayOfMonth);
[Register(".ctor", "(Landroid/content/Context;Landroid/app/DatePickerDialog$OnDateSetListener;III)V", "")]
public DatePickerDialog(Context context, IOnDateSetListener? listener, int year, int month, int dayOfMonth);
public DatePickerDialog(Context context, int theme, EventHandler<DateSetEventArgs> callBack, int year, int monthOfYear, int dayOfMonth);
[Register(".ctor", "(Landroid/content/Context;ILandroid/app/DatePickerDialog$OnDateSetListener;III)V", "")]
public DatePickerDialog(Context context, int themeResId, IOnDateSetListener? listener, int year, int monthOfYear, int dayOfMonth);
protected DatePickerDialog(IntPtr javaReference, JniHandleOwnership transfer);
public virtual DatePicker DatePicker { get; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override JniPeerMembers JniPeerMembers { get; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[EditorBrowsable(EditorBrowsableState.Never)]
protected override Type ThresholdType { get; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[EditorBrowsable(EditorBrowsableState.Never)]
protected override IntPtr ThresholdClass { get; }
public event EventHandler<DateSetEventArgs> DateSet;
[Register("onClick", "(Landroid/content/DialogInterface;I)V", "GetOnClick_Landroid_content_DialogInterface_IHandler")]
public virtual void OnClick(IDialogInterface dialog, int which);
[Register("onDateChanged", "(Landroid/widget/DatePicker;III)V", "GetOnDateChanged_Landroid_widget_DatePicker_IIIHandler")]
public virtual void OnDateChanged(DatePicker view, int year, int month, int dayOfMonth);
[Register("setOnDateSetListener", "(Landroid/app/DatePickerDialog$OnDateSetListener;)V", "GetSetOnDateSetListener_Landroid_app_DatePickerDialog_OnDateSetListener_Handler", ApiSince = 24)]
public virtual void SetOnDateSetListener(IOnDateSetListener? listener);
public void UpdateDate(DateTime date);
[Register("updateDate", "(III)V", "GetUpdateDate_IIIHandler")]
public virtual void UpdateDate(int year, int month, int dayOfMonth);
[Register("android/app/DatePickerDialog$OnDateSetListener", "", "Android.App.DatePickerDialog/IOnDateSetListenerInvoker")]
public interface IOnDateSetListener : IJavaObject, IDisposable, IJavaPeerable
{
[Register("onDateSet", "(Landroid/widget/DatePicker;III)V", "GetOnDateSet_Landroid_widget_DatePicker_IIIHandler:Android.App.DatePickerDialog/IOnDateSetListenerInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")]
void OnDateSet(DatePicker? view, int year, int month, int dayOfMonth);
}
public class DateSetEventArgs : EventArgs
{
public DateSetEventArgs(int year, int month, int dayOfMonth);
public DateTime Date { get; }
[Obsolete("This parameter in DateTimePickerDialog constructor is removed in Android API, so it will vanish from this automatically generated type too.")]
public int MonthOfYear { get; }
public int Year { get; }
public int Month { get; }
public int DayOfMonth { get; }
}
}
}
I saw that the IOnDateSetListener
interface was being defined right in the class. I also noticed the class was no longer marked partial
.
I then was also able to find DatePickerDialog IOnDateSetListener.xml
:
<Type Name="DatePickerDialog IOnDateSetListener" FullName="Android.App.DatePickerDialog IOnDateSetListener">
<TypeSignature Language="C#" Value="public interface DatePickerDialog.IOnDateSetListener : Android.Runtime.IJavaObject, IDisposable, Java.Interop.IJavaPeerable" />
<TypeSignature Language="ILAsm" Value=".class nested public interface auto ansi abstract DatePickerDialog/IOnDateSetListener implements class Android.Runtime.IJavaObject, class Java.Interop.IJavaPeerable, class System.IDisposable" />
<TypeSignature Language="DocId" Value="T:Android.App.DatePickerDialog.IOnDateSetListener" />
<TypeSignature Language="F#" Value="type DatePickerDialog.IOnDateSetListener = interface
 interface IJavaObject
 interface IDisposable
 interface IJavaPeerable" />
...
<AttributeName Language="C#">[Android.Runtime.Register("android/app/DatePickerDialog$OnDateSetListener", "", "Android.App.DatePickerDialog/IOnDateSetListenerInvoker")]</AttributeName>
<AttributeName Language="F#">[<Android.Runtime.Register("android/app/DatePickerDialog$OnDateSetListener", "", "Android.App.DatePickerDialog/IOnDateSetListenerInvoker")>]</AttributeName>
...
<a href="https://developer.android.com/reference/android/app/DatePickerDialog$OnDateSetListener" title="Reference documentation">Android platform documentation</a>
...
and DatePickerDialog.xml
:
<Type Name="DatePickerDialog" FullName="Android.App.DatePickerDialog">
<TypeSignature Language="C#" Value="public class DatePickerDialog : Android.App.AlertDialog, Android.Content.IDialogInterfaceOnClickListener, Android.Widget.DatePicker.IOnDateChangedListener, IDisposable, Java.Interop.IJavaPeerable" />
<TypeSignature Language="ILAsm" Value=".class public auto ansi beforefieldinit DatePickerDialog extends Android.App.AlertDialog implements class Android.Content.IDialogInterfaceOnClickListener, class Android.Runtime.IJavaObject, class Android.Widget.DatePicker/IOnDateChangedListener, class Java.Interop.IJavaPeerable, class System.IDisposable" />
<TypeSignature Language="DocId" Value="T:Android.App.DatePickerDialog" />
<TypeSignature Language="F#" Value="type DatePickerDialog = class
 inherit AlertDialog
 interface IDialogInterfaceOnClickListener
 interface IJavaObject
 interface IDisposable
 interface IJavaPeerable
 interface DatePicker.IOnDateChangedListener" />
<AssemblyInfo>
<AssemblyName>Mono.Android</AssemblyName>
<AssemblyVersion>0.0.0.0</AssemblyVersion>
</AssemblyInfo>
<Base>
<BaseTypeName>Android.App.AlertDialog</BaseTypeName>
</Base>
<Interfaces>
<Interface>
<InterfaceName>Android.Content.IDialogInterfaceOnClickListener</InterfaceName>
</Interface>
<Interface>
<InterfaceName>Android.Runtime.IJavaObject</InterfaceName>
</Interface>
<Interface>
<InterfaceName>Android.Widget.DatePicker IOnDateChangedListener</InterfaceName>
</Interface>
...
Both of the aforementioned files mention it. My best guess would be that there are build events that use this information to build cross-language methods and inject them into partial classes. The c# DatePickerDialog
(from metadata) may be the result of compile time events merging the DatePickerDialog
- mentioned in the question - with the information contained in the XML files when the Xamarin libraries are built and packaged.
The listener interfaces seem to be specific to the behavior of DatePicker
and TimePicker
, and don't seem to be necessary in every situation.
Instead of trying to replicate that behavior, creating a custom dialog that extends AlertDialog
and calling the SetView
method may be all that is needed.
EDIT:
There is also a Popup
class included in the Xamarin Community Toolkit that makes it very easy to make custom dialogs like the ones referenced in the question. See the documentation here.