Ikat ke metode di WPF?

90

Bagaimana Anda mengikat metode objek dalam skenario ini di WPF?

public class RootObject
{
    public string Name { get; }

    public ObservableCollection<ChildObject> GetChildren() {...}
}

public class ChildObject
{
    public string Name { get; }
}

XAML:

<TreeView ItemsSource="some list of RootObjects">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type data:RootObject}" 
                                  ItemsSource="???">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type data:ChildObject}">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>

Di sini saya ingin mengikat GetChildrenmetode pada masing RootObject- masing pohon.

EDIT Binding ke an ObjectDataProvidertampaknya tidak berfungsi karena saya mengikat daftar item, dan ObjectDataProvidermemerlukan metode statis, atau membuat instance sendiri dan menggunakannya.

Misalnya, menggunakan jawaban Matt saya mendapatkan:

Kesalahan System.Windows.Data: 33: ObjectDataProvider tidak dapat membuat objek; Ketik = 'RootObject'; Kesalahan = 'Parameter salah untuk konstruktor.'

Kesalahan System.Windows.Data: 34: ObjectDataProvider: Kegagalan mencoba memanggil metode pada tipe; Metode = 'GetChildren'; Ketik = 'RootObject'; Error = 'Anggota yang ditentukan tidak dapat dipanggil pada target.' TargetException: 'System.Reflection.TargetException: Metode non-statis memerlukan target.

Cameron MacFarland
sumber
Ya kamu benar. ObjectDataProvider memang memiliki properti ObjectInstance (untuk memanggil metodenya pada instance tertentu) tetapi menurut saya itu bukan properti ketergantungan, jadi Anda tidak dapat mengikatnya (AFAIK).
Matt Hamilton
1
Ya, saya mencoba mengikat ke ObjectInstance dan menemukan itu bukan properti ketergantungan.
Cameron MacFarland
Saya akan meninggalkan jawaban saya di sana, baik untuk memberikan update Anda beberapa konteks dan untuk membantu orang lain yang menemukan pertanyaan ini dengan masalah yang cukup mirip.
Matt Hamilton
Apakah Anda benar-benar perlu terikat ke ObjectInstance? (Apakah itu akan berubah) Dengan asumsi Anda dapat membuat sendiri penanganan peristiwa perubahan dan memperbarui ObjectDataProvider dalam kode ...
Tim Lovell-Smith
1
Baru saja memperbarui jawaban saya dengan beberapa kode sumber, setahun setelah fakta.
Drew Noakes

Jawaban:

71

Pendekatan lain yang mungkin berhasil untuk Anda adalah membuat kustom IValueConverteryang menggunakan nama metode sebagai parameter, sehingga akan digunakan seperti ini:

ItemsSource="{Binding 
    Converter={StaticResource MethodToValueConverter},
    ConverterParameter='GetChildren'}"

Konverter ini akan mencari dan memanggil metode menggunakan refleksi. Ini membutuhkan metode untuk tidak memiliki argumen apa pun.

Berikut adalah contoh sumber konverter tersebut:

public sealed class MethodToValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var methodName = parameter as string;
        if (value==null || methodName==null)
            return value;
        var methodInfo = value.GetType().GetMethod(methodName, new Type[0]);
        if (methodInfo==null)
            return value;
        return methodInfo.Invoke(value, new object[0]);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException("MethodToValueConverter can only be used for one way conversion.");
    }
}

Dan tes unit yang sesuai:

[Test]
public void Convert()
{
    var converter = new MethodToValueConverter();
    Assert.AreEqual("1234", converter.Convert(1234, typeof(string), "ToString", null));
    Assert.AreEqual("ABCD", converter.Convert(" ABCD ", typeof(string), "Trim", null));

    Assert.IsNull(converter.Convert(null, typeof(string), "ToString", null));

    Assert.AreEqual("Pineapple", converter.Convert("Pineapple", typeof(string), "InvalidMethodName", null));
}

Perhatikan bahwa konverter ini tidak memberlakukan targetTypeparameter.

Drew Noakes
sumber
6
Hmmm, ... sepertinya hack tapi saya mulai berpikir ini mungkin satu-satunya cara. Ini pasti akan menjadi yang termudah!
EightyOne Unite
25

Tidak yakin seberapa baik ini akan bekerja dalam skenario Anda, tetapi Anda dapat menggunakan MethodNameproperti on ObjectDataProvideruntuk memanggil metode tertentu (dengan parameter tertentu dari MethodParametersproperti Anda ) untuk mengambil datanya.

Berikut cuplikan yang diambil langsung dari halaman MSDN:

<Window.Resources>
    <ObjectDataProvider ObjectType="{x:Type local:TemperatureScale}"
        MethodName="ConvertTemp" x:Key="convertTemp">
        <ObjectDataProvider.MethodParameters>
            <system:Double>0</system:Double>
            <local:TempType>Celsius</local:TempType>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

Jadi itulah ObjectDataProvideryang memanggil ConvertTempmetode pada instance TemperatureScalekelas, meneruskan dua parameter ( 0dan TempType.Celsius).

Matt Hamilton
sumber
11

Apakah Anda harus terikat pada metode ini?

Bisakah Anda mengikat ke properti yang getter adalah metodenya?

public ObservableCollection<ChildObject> Children
{
   get
   {
      return GetChildren();
   }
}
Michael Prewecki
sumber
2
Saya mengartikan komentar Cameron bahwa dia mengikat pada tipe yang dia tidak dapat menambahkan propertinya.
Drew Noakes
2
Anda harus menghindari pengikatan ke properti yang memanggil metode khususnya jika metode tersebut berpotensi berjalan lama. Memiliki metode seperti itu, properti bukanlah desain yang baik karena konsumen kode mengharapkan properti hanya mengakses variabel lokal.
markmnl
@ Markmnl jadi apa gunanya mengikat langsung ke fungsi? Jadi pertanyaan OP tidak masuk akal dalam kasus Anda?
Teoman shipahi
4

Kecuali Anda dapat menambahkan properti untuk memanggil metode (atau membuat kelas pembungkus yang menambahkan properti itu) satu-satunya cara yang saya tahu adalah menggunakan ValueConverter.

Nir
sumber
3

ObjectDataProvider juga memiliki properti ObjectInstance yang bisa digunakan sebagai pengganti ObjectType

Graham Ambrose
sumber
3

Anda dapat menggunakan System.ComponentModeluntuk mendefinisikan properti untuk suatu tipe secara dinamis (mereka bukan bagian dari metadata yang dikompilasi). Saya menggunakan pendekatan ini di WPF untuk mengaktifkan pengikatan ke jenis yang menyimpan nilainya di bidang, karena pengikatan ke bidang tidak dimungkinkan.

Jenis ICustomTypeDescriptordan TypeDescriptionProvidermemungkinkan Anda mencapai apa yang Anda inginkan. Menurut artikel ini :

TypeDescriptionProvidermemungkinkan Anda untuk menulis kelas terpisah yang mengimplementasikan ICustomTypeDescriptordan kemudian mendaftarkan kelas ini sebagai penyedia deskripsi untuk tipe lain.

Saya sendiri belum mencoba pendekatan ini, tetapi saya harap ini membantu dalam kasus Anda.

Drew Noakes
sumber
0

Untuk mengikat ke metode objek dalam skenario WPF, Anda dapat mengikat ke properti yang mengembalikan delegasi.

Austin_Anderson
sumber