MonoDroid: ошибка при вызове конструктора пользовательского представления - TwoDScrollView

Я создаю приложение для Android, которое использует специально созданный TwoDScrollView, найденный здесь:

http://blog.gorges.us/2010/06/android-two-dimensional-scrollview/

Этот же класс можно найти, ссылаясь на несколько других веб-сайтов, а другие на Qaru задали вопросы по этому поводу. Я использовал его в предыдущем приложении для Android, которое я создавал с использованием Java/Eclipse, и у меня был успех.

В моем текущем приложении я хотел использовать С# и MonoDroid. Я решил переписать весь класс TwoDScrollView на С#. После его перезаписи, а затем, используя его в XML-макете, я получаю следующие исключения при попытке запустить мой код:

Исправлено System.NotSupportedException. Не удалось активировать экземпляр типа MyProject.TwoDScrollView из встроенного дескриптора 44f4d310.

System.Exception: конструктор не найден для MyProject.TwoDScrollView::. Т е р (System.IntPtr, Android.Runtime.JniHandleOwnership)...... с большим текстом, который следует....

Мой макет XML выглядит следующим образом:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

<myproject.TwoDScrollView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

</myproject.TwoDScrollView>

</RelativeLayout>

В соответствии с инструкциями по следующей ссылке, используя пользовательские представления в формате XML в MonoDroid: http://docs.xamarin.com/android/advanced_topics/using_custom_views_in_a_layout

Конструкторы класса TwoDScrollView выглядят следующим образом:

public TwoDScrollView(Context context) 
    : base(context)
{
    initTwoDScrollView();
}

public TwoDScrollView(Context context, IAttributeSet attrs) 
    : base(context, attrs)
{
    initTwoDScrollView();
}

public TwoDScrollView(Context context, IAttributeSet attrs, int defStyle) 
    : base(context, attrs, defStyle)
{
    initTwoDScrollView();
}

В версии С# существуют те же самые конструкторы, что и в версии Java (которую вы можете найти в приведенной выше ссылке). Любая идея о том, что может пойти не так? Я могу опубликовать полный код С# моего TwoDScrollView, если кто-то захочет его увидеть. Это по сути то же, что и бит кода Java для бит, кроме перезаписываемого в С#.

Спасибо за любую помощь!

Ответ 1

Поздравляем! Вы попали в пропущенную абстракцию.: -/

Проблема заключается в следующем: для лучшего или худшего, вызовы виртуальных методов от конструкторов вызывают реализацию самого производного метода. С# - это то же самое, что и Java; рассмотрите следующую программу:

using System;

class Base {
    public Base ()
    {
        Console.WriteLine ("Base..ctor");
        M ();
    }

    public virtual void M ()
    {
        Console.WriteLine ("Base.M");
    }
}

class Derived : Base {

    public Derived ()
    {
        Console.WriteLine ("Derived..ctor");
    }

    public override void M ()
    {
        Console.WriteLine ("Derived.M");
    }
}

static class Demo {
    public static void Main ()
    {
        new Derived ();
    }
}

При запуске вывод:

Base..ctor
Derived.M
Derived..ctor

То есть, метод Derived.M() вызывается перед выполнением конструктора Derived.

В Mono для Android все становится еще сложнее. Конструктор Android Callable Wrapper (ACW) вызывается Java и отвечает за создание peer С# и сопоставление экземпляра Java с экземпляром С#. Однако, если виртуальный метод вызывается из конструктора Java, тогда метод будет отправлен до того, как будет экземпляр С# для вызова метода на!

Пусть это немного утонет.

Я не знаю, какой метод запускает сценарий для вашего конкретного кода (фрагмент кода, который вы предоставили, отлично работает), но у нас есть образец, который использует этот сценарий: LogTextBox переопределяет TextView.DefaultMovementMethod, а конструктор TextView вызывает метод getDefaultMovementMethod(). В результате Android пытается вызвать LogTextBox.DefaultMovementMethod до появления экземпляра LogTextBox.

Итак, что делает Mono для Android? Mono для Android создал ACW и, таким образом, знает, какой тип С# должен использовать метод getDefaultMovementMethod(). То, что у него нет, есть экземпляр, потому что он не был создан. Поэтому Mono для Android создает экземпляр соответствующего типа... через конструктор (IntPtr, JniHandleOwnership) и генерирует ошибку, если этот конструктор не найден.

Как только завершится выполнение конструктора TextView (в этом случае) TextView, будет выполнен конструктор ACW LogTextBox, после чего Mono для Android пойдет "ага! мы уже создали экземпляр С# для этого экземпляра Java" , и затем вызовет соответствующий конструктор на уже созданном экземпляре. Это означает, что для одного экземпляра будут выполняться два конструктора: конструктор (IntPtr, JniHandleOwnership) и (позже) (Context, IAttributeSet, int) конструктор.

Ответ 2

В сообщении об ошибке говорится:

System.Exception: No constructor found for MyProject.TwoDScrollView::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)

Попробуйте добавить конструктор, как он говорит, и посмотрите, помогает ли это:

public TwoDScrollView (IntPtr a, JniHandleOwnership b) : base (a, b) { }

Ответ 3

У меня была та же проблема с пользовательским представлением изображения, и ответ для jpobst, безусловно, полностью устранил проблему:

public CircularImageView(Context context)
            :base(context) 
        {

            init (context, null, 0);
        }

        public CircularImageView(Context context, IAttributeSet attrs)
            : base(context, attrs)
        {
            init (context, attrs, Resource.Attribute.circularImageViewStyle);
        }

        public CircularImageView(Context context, IAttributeSet attrs, int defStyle)
            :base(context, attrs, defStyle)
        {

            init(context, attrs, defStyle);
        }
        public CircularImageView (IntPtr a, JniHandleOwnership b) : base (a, b)
        {
        }