Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym temacie pokazano, jak powiązać kontrolkę (lub inny element interfejsu użytkownika) z pojedynczym elementem lub powiązać kontrolkę elementów z kolekcją elementów w aplikacji platformy uniwersalnej systemu Windows (UWP). Ponadto pokazujemy, jak kontrolować renderowanie elementów, implementować widok szczegółów na podstawie zaznaczenia i konwertować dane na potrzeby wyświetlania. Aby uzyskać bardziej szczegółowe informacje, zobacz Dogłębne powiązanie danych.
Wymagania wstępne
W tym temacie założono, że wiesz, jak utworzyć podstawową aplikację platformy UWP. Aby uzyskać instrukcje dotyczące tworzenia pierwszej aplikacji platformy UWP, zobacz Wprowadzenie do aplikacji systemu Windows.
Tworzenie projektu
Utwórz nowy projekt aplikacji
Wiązanie z pojedynczym elementem
Każde powiązanie składa się z elementu docelowego powiązania i źródła powiązania. Zazwyczaj elementem docelowym jest właściwość kontrolki lub innego elementu interfejsu użytkownika, a źródłem jest właściwość wystąpienia klasy (model danych lub model widoku). W tym przykładzie pokazano, jak powiązać kontrolkę z jednym elementem. Obiektem docelowym jest właściwość Text w TextBlock. Źródłem jest instancja prostej klasy o nazwie Recording, która reprezentuje nagranie audio. Przyjrzyjmy się najpierw klasie.
Jeśli używasz języka C# lub C++/CX, dodaj nową klasę do projektu i nadaj klasie nazwę Recording.
Jeśli używasz C++/WinRT, dodaj nowe elementy plików Midl (.idl) do projektu, nazwane zgodnie z poniższym przykładem kodu C++/WinRT. Zastąp zawartość tych nowych plików kodem MIDL 3.0 pokazanym w zestawieniu, skompiluj projekt w celu wygenerowania Recording.h, .cpp, RecordingViewModel.h i .cpp, a następnie dodaj kod do wygenerowanych plików, aby pasował do zestawienia. Aby uzyskać więcej informacji o wygenerowanych plikach i sposobie ich kopiowania do projektu, zobacz kontrolek XAML; powiązanie z właściwością C++/WinRT.
namespace Quickstart
{
public class Recording
{
public string ArtistName { get; set; }
public string CompositionName { get; set; }
public DateTime ReleaseDateTime { get; set; }
public Recording()
{
this.ArtistName = "Wolfgang Amadeus Mozart";
this.CompositionName = "Andante in C for Piano";
this.ReleaseDateTime = new DateTime(1761, 1, 1);
}
public string OneLineSummary
{
get
{
return $"{this.CompositionName} by {this.ArtistName}, released: "
+ this.ReleaseDateTime.ToString("d");
}
}
}
public class RecordingViewModel
{
private Recording defaultRecording = new Recording();
public Recording DefaultRecording { get { return this.defaultRecording; } }
}
}
// Recording.idl
namespace Quickstart
{
runtimeclass Recording
{
Recording(String artistName, String compositionName, Windows.Globalization.Calendar releaseDateTime);
String ArtistName{ get; };
String CompositionName{ get; };
Windows.Globalization.Calendar ReleaseDateTime{ get; };
String OneLineSummary{ get; };
}
}
// RecordingViewModel.idl
import "Recording.idl";
namespace Quickstart
{
runtimeclass RecordingViewModel
{
RecordingViewModel();
Quickstart.Recording DefaultRecording{ get; };
}
}
// Recording.h
// Add these fields:
...
#include <sstream>
...
private:
std::wstring m_artistName;
std::wstring m_compositionName;
Windows::Globalization::Calendar m_releaseDateTime;
...
// Recording.cpp
// Implement like this:
...
Recording::Recording(hstring const& artistName, hstring const& compositionName, Windows::Globalization::Calendar const& releaseDateTime) :
m_artistName{ artistName.c_str() },
m_compositionName{ compositionName.c_str() },
m_releaseDateTime{ releaseDateTime } {}
hstring Recording::ArtistName(){ return hstring{ m_artistName }; }
hstring Recording::CompositionName(){ return hstring{ m_compositionName }; }
Windows::Globalization::Calendar Recording::ReleaseDateTime(){ return m_releaseDateTime; }
hstring Recording::OneLineSummary()
{
std::wstringstream wstringstream;
wstringstream << m_compositionName.c_str();
wstringstream << L" by " << m_artistName.c_str();
wstringstream << L", released: " << m_releaseDateTime.MonthAsNumericString().c_str();
wstringstream << L"/" << m_releaseDateTime.DayAsString().c_str();
wstringstream << L"/" << m_releaseDateTime.YearAsString().c_str();
return hstring{ wstringstream.str().c_str() };
}
...
// RecordingViewModel.h
// Add this field:
...
#include "Recording.h"
...
private:
Quickstart::Recording m_defaultRecording{ nullptr };
...
// RecordingViewModel.cpp
// Implement like this:
...
Quickstart::Recording RecordingViewModel::DefaultRecording()
{
Windows::Globalization::Calendar releaseDateTime;
releaseDateTime.Year(1761);
releaseDateTime.Month(1);
releaseDateTime.Day(1);
m_defaultRecording = winrt::make<Recording>(L"Wolfgang Amadeus Mozart", L"Andante in C for Piano", releaseDateTime);
return m_defaultRecording;
}
...
// Recording.h
#include <sstream>
namespace Quickstart
{
public ref class Recording sealed
{
private:
Platform::String^ artistName;
Platform::String^ compositionName;
Windows::Globalization::Calendar^ releaseDateTime;
public:
Recording(Platform::String^ artistName, Platform::String^ compositionName,
Windows::Globalization::Calendar^ releaseDateTime) :
artistName{ artistName },
compositionName{ compositionName },
releaseDateTime{ releaseDateTime } {}
property Platform::String^ ArtistName
{
Platform::String^ get() { return this->artistName; }
}
property Platform::String^ CompositionName
{
Platform::String^ get() { return this->compositionName; }
}
property Windows::Globalization::Calendar^ ReleaseDateTime
{
Windows::Globalization::Calendar^ get() { return this->releaseDateTime; }
}
property Platform::String^ OneLineSummary
{
Platform::String^ get()
{
std::wstringstream wstringstream;
wstringstream << this->CompositionName->Data();
wstringstream << L" by " << this->ArtistName->Data();
wstringstream << L", released: " << this->ReleaseDateTime->MonthAsNumericString()->Data();
wstringstream << L"/" << this->ReleaseDateTime->DayAsString()->Data();
wstringstream << L"/" << this->ReleaseDateTime->YearAsString()->Data();
return ref new Platform::String(wstringstream.str().c_str());
}
}
};
public ref class RecordingViewModel sealed
{
private:
Recording ^ defaultRecording;
public:
RecordingViewModel()
{
Windows::Globalization::Calendar^ releaseDateTime = ref new Windows::Globalization::Calendar();
releaseDateTime->Year = 1761;
releaseDateTime->Month = 1;
releaseDateTime->Day = 1;
this->defaultRecording = ref new Recording{ L"Wolfgang Amadeus Mozart", L"Andante in C for Piano", releaseDateTime };
}
property Recording^ DefaultRecording
{
Recording^ get() { return this->defaultRecording; };
}
};
}
// Recording.cpp
#include "pch.h"
#include "Recording.h"
Następnie uwidocznij klasę źródłową powiązania w klasie reprezentującej stronę z znacznikami. Robimy to, dodając właściwość typu RecordingViewModel do strony MainPage.
Jeśli używaszMainPage.h i .cpp, a następnie scal zmiany w tych wygenerowanych plikach do tych w projekcie.
namespace Quickstart
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new RecordingViewModel();
}
public RecordingViewModel ViewModel{ get; set; }
}
}
// MainPage.idl
// Add this property:
import "RecordingViewModel.idl";
...
RecordingViewModel ViewModel{ get; };
...
// MainPage.h
// Add this property and this field:
...
#include "RecordingViewModel.h"
...
Quickstart::RecordingViewModel ViewModel();
private:
Quickstart::RecordingViewModel m_viewModel{ nullptr };
...
// MainPage.cpp
// Implement like this:
...
MainPage::MainPage()
{
InitializeComponent();
m_viewModel = winrt::make<RecordingViewModel>();
}
Quickstart::RecordingViewModel MainPage::ViewModel()
{
return m_viewModel;
}
...
// MainPage.h
...
#include "Recording.h"
namespace Quickstart
{
public ref class MainPage sealed
{
private:
RecordingViewModel ^ viewModel;
public:
MainPage();
property RecordingViewModel^ ViewModel
{
RecordingViewModel^ get() { return this->viewModel; };
}
};
}
// MainPage.cpp
...
MainPage::MainPage()
{
InitializeComponent();
this->viewModel = ref new RecordingViewModel();
}
Ostatnim elementem jest powiązanie TextBlock z właściwością ViewModel.DefaultRecording.OneLineSummary.
<Page x:Class="Quickstart.MainPage" ... >
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="{x:Bind ViewModel.DefaultRecording.OneLineSummary}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Page>
Jeśli używasz języka C++/WinRT, musisz usunąć funkcję MainPage::ClickHandler w celu skompilowania projektu.
Oto wynik.
Wiązanie z kolekcją elementów
Typowym scenariuszem jest powiązanie z kolekcją obiektów biznesowych. W językach C# i Visual Basic klasa generyczna ObservableCollection<T> jest dobrym wyborem dla powiązania danych, ponieważ implementuje interfejsy INotifyPropertyChanged i INotifyCollectionChanged. Te interfejsy zapewniają powiadomienie o zmianie powiązania, gdy elementy są dodawane lub usuwane, albo zmienia się właściwość samej listy. Jeśli chcesz, aby powiązane kontrolki zostały zaktualizowane o zmiany właściwości obiektów w kolekcji, obiekt biznesowy powinien również zaimplementować INotifyPropertyChanged. Aby uzyskać więcej informacji, zobacz Dogłębne powiązanie danych.
Jeśli używasz C++/WinRT, możesz dowiedzieć się więcej o powiązaniu z obserwowalnymi kolekcjami w kontrolkach elementów XAML ; wiązanie z kolekcją C++/WinRT. Jeśli najpierw przeczytasz ten temat, intencja kodu C++/WinRT pokazana poniżej będzie jaśniejsza.
W następnym przykładzie ListView jest powiązany z kolekcją obiektów Recording. Zacznijmy od dodania kolekcji do naszego modelu wyświetlania. Wystarczy tylko dodać te nowe pola do klasy RecordingViewModel.
public class RecordingViewModel
{
...
private ObservableCollection<Recording> recordings = new ObservableCollection<Recording>();
public ObservableCollection<Recording> Recordings{ get{ return this.recordings; } }
public RecordingViewModel()
{
this.recordings.Add(new Recording(){ ArtistName = "Johann Sebastian Bach",
CompositionName = "Mass in B minor", ReleaseDateTime = new DateTime(1748, 7, 8) });
this.recordings.Add(new Recording(){ ArtistName = "Ludwig van Beethoven",
CompositionName = "Third Symphony", ReleaseDateTime = new DateTime(1805, 2, 11) });
this.recordings.Add(new Recording(){ ArtistName = "George Frideric Handel",
CompositionName = "Serse", ReleaseDateTime = new DateTime(1737, 12, 3) });
}
}
// RecordingViewModel.idl
// Add this property:
...
#include <winrt/Windows.Foundation.Collections.h>
...
Windows.Foundation.Collections.IVector<IInspectable> Recordings{ get; };
...
// RecordingViewModel.h
// Change the constructor declaration, and add this property and this field:
...
RecordingViewModel();
Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> Recordings();
private:
Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> m_recordings;
...
// RecordingViewModel.cpp
// Update/add implementations like this:
...
RecordingViewModel::RecordingViewModel()
{
std::vector<Windows::Foundation::IInspectable> recordings;
Windows::Globalization::Calendar releaseDateTime;
releaseDateTime.Month(7); releaseDateTime.Day(8); releaseDateTime.Year(1748);
recordings.push_back(winrt::make<Recording>(L"Johann Sebastian Bach", L"Mass in B minor", releaseDateTime));
releaseDateTime = Windows::Globalization::Calendar{};
releaseDateTime.Month(11); releaseDateTime.Day(2); releaseDateTime.Year(1805);
recordings.push_back(winrt::make<Recording>(L"Ludwig van Beethoven", L"Third Symphony", releaseDateTime));
releaseDateTime = Windows::Globalization::Calendar{};
releaseDateTime.Month(3); releaseDateTime.Day(12); releaseDateTime.Year(1737);
recordings.push_back(winrt::make<Recording>(L"George Frideric Handel", L"Serse", releaseDateTime));
m_recordings = winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>(std::move(recordings));
}
Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> RecordingViewModel::Recordings() { return m_recordings; }
...
// Recording.h
...
public ref class RecordingViewModel sealed
{
private:
...
Windows::Foundation::Collections::IVector<Recording^>^ recordings;
public:
RecordingViewModel()
{
...
releaseDateTime = ref new Windows::Globalization::Calendar();
releaseDateTime->Year = 1748;
releaseDateTime->Month = 7;
releaseDateTime->Day = 8;
Recording^ recording = ref new Recording{ L"Johann Sebastian Bach", L"Mass in B minor", releaseDateTime };
this->Recordings->Append(recording);
releaseDateTime = ref new Windows::Globalization::Calendar();
releaseDateTime->Year = 1805;
releaseDateTime->Month = 2;
releaseDateTime->Day = 11;
recording = ref new Recording{ L"Ludwig van Beethoven", L"Third Symphony", releaseDateTime };
this->Recordings->Append(recording);
releaseDateTime = ref new Windows::Globalization::Calendar();
releaseDateTime->Year = 1737;
releaseDateTime->Month = 12;
releaseDateTime->Day = 3;
recording = ref new Recording{ L"George Frideric Handel", L"Serse", releaseDateTime };
this->Recordings->Append(recording);
}
...
property Windows::Foundation::Collections::IVector<Recording^>^ Recordings
{
Windows::Foundation::Collections::IVector<Recording^>^ get()
{
if (this->recordings == nullptr)
{
this->recordings = ref new Platform::Collections::Vector<Recording^>();
}
return this->recordings;
};
}
};
Następnie powiąż ListView z właściwością ViewModel.Recordings.
<Page x:Class="Quickstart.MainPage" ... >
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Page>
Nie udostępniliśmy jeszcze szablonu danych dla klasy Recording, więc najlepsze, co może zrobić struktura interfejsu użytkownika, to wywołać ToString dla każdego elementu w ListView. Domyślną implementacją ToString jest zwrócenie nazwy typu.
Aby rozwiązać ten problem, możemy zastąpić ToString, aby zwrócić wartość OneLineSummarylub udostępnić szablon danych. Opcja szablonu danych jest bardziej zwykłym rozwiązaniem i bardziej elastycznym rozwiązaniem. Szablon danych określa się przy użyciu właściwości ContentTemplate kontrolki zawartości lub właściwości ItemTemplate kontrolki elementów. Poniżej przedstawiono dwa sposoby projektowania szablonu danych na potrzeby nagrywania wraz z ilustracją wyniku.
<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Recording">
<TextBlock Text="{x:Bind OneLineSummary}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Recording">
<StackPanel Orientation="Horizontal" Margin="6">
<SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
<StackPanel>
<TextBlock Text="{x:Bind ArtistName}" FontWeight="Bold"/>
<TextBlock Text="{x:Bind CompositionName}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Aby uzyskać więcej informacji na temat składni XAML, zobacz
Dodawanie widoku szczegółów
Możesz wyświetlić wszystkie szczegóły obiektów nagrywania
Istnieją dwa sposoby, aby to zrobić. Możesz powiązać widok szczegółów z właściwością SelectedItem obiektu ListView. Możesz też użyć CollectionViewSource, w takim przypadku powiążesz zarówno ListView, jak i widok szczegółów z CollectionViewSource (co ułatwi zarządzanie aktualnie wybranym elementem). Obie techniki są pokazane poniżej i obie dają te same wyniki (pokazane na ilustracji).
Uwaga / Notatka
Do tej pory w tym temacie używaliśmy tylko rozszerzenia znaczników {x:Bind}, ale obie techniki, które pokażemy poniżej, wymagają bardziej elastycznego (choć mniej wydajnego) rozszerzenia znaczników {Binding}.
Jeśli używasz rozszerzeń komponentów C++/WinRT lub Visual C++ (C++/CX), aby móc używać rozszerzenia znaczników {Binding}, musisz dodać atrybut BindableAttribute do dowolnej klasy wykonywalnej, z którą chcesz powiązać. Aby użyć elementu {x:Bind}, nie potrzebujesz tego atrybutu.
Ważne
Jeśli używasz języka C++/WinRT, atrybut BindableAttribute jest dostępny, jeśli zainstalowano zestaw Windows SDK w wersji 10.0.17763.0 (Windows 10, wersja 1809) lub nowszym. Bez tego atrybutu należy zaimplementować interfejsy ICustomPropertyProvider i ICustomProperty, aby móc używać rozszerzenia znaczników {Binding}.
Najpierw poniżej przedstawiono technikę SelectedItem.
// No code changes necessary for C#.
// Recording.idl
// Add this attribute:
...
[Windows.UI.Xaml.Data.Bindable]
runtimeclass Recording
...
[Windows::UI::Xaml::Data::Bindable]
public ref class Recording sealed
{
...
};
Jedyną inną konieczną zmianą jest zmiana w znacznikach.
<Page x:Class="Quickstart.MainPage" ... >
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView x:Name="recordingsListView" ItemsSource="{x:Bind ViewModel.Recordings}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Recording">
<StackPanel Orientation="Horizontal" Margin="6">
<SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
<StackPanel>
<TextBlock Text="{x:Bind CompositionName}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel DataContext="{Binding SelectedItem, ElementName=recordingsListView}"
Margin="0,24,0,0">
<TextBlock Text="{Binding ArtistName}"/>
<TextBlock Text="{Binding CompositionName}"/>
<TextBlock Text="{Binding ReleaseDateTime}"/>
</StackPanel>
</StackPanel>
</Grid>
</Page>
W przypadku techniki CollectionViewSource , najpierw dodaj CollectionViewSource jako zasób strony.
<Page.Resources>
<CollectionViewSource x:Name="RecordingsCollection" Source="{x:Bind ViewModel.Recordings}"/>
</Page.Resources>
Następnie dostosuj powiązania w ListView (które nie muszą już być nazwane) i w widoku szczegółów, by użyć CollectionViewSource. Należy pamiętać, że powiązanie widoku szczegółów bezpośrednio z CollectionViewSourceoznacza, że chcesz powiązać z bieżącym elementem w powiązaniach, w których nie można odnaleźć ścieżki w samej kolekcji. Nie ma potrzeby określania właściwości CurrentItem jako ścieżki dla powiązania, chociaż można to zrobić, jeśli istnieje niejednoznaczność).
...
<ListView ItemsSource="{Binding Source={StaticResource RecordingsCollection}}">
...
<StackPanel DataContext="{Binding Source={StaticResource RecordingsCollection}}" ...>
...
I oto identyczny wynik w każdym przypadku.
Uwaga / Notatka
Jeśli używasz języka C++, interfejs użytkownika nie będzie wyglądać dokładnie tak jak na poniższej ilustracji: renderowanie właściwości ReleaseDateTime jest inne. Więcej informacji na ten temat można znaleźć w poniższej sekcji.
Formatowanie lub konwertowanie wartości danych na potrzeby wyświetlania
Wystąpił problem z renderowaniem powyżej. Właściwość ReleaseDateTime nie jest tylko datą, jest to DateTime (jeśli używasz języka C++, jest to Kalendarz). Dlatego w języku C#jest wyświetlany z większą precyzją niż potrzebujemy. W języku C++ jest renderowany jako nazwa typu. Jednym z rozwiązań jest dodanie właściwości string do klasy Recording , która zwraca odpowiednik klasy this.ReleaseDateTime.ToString("d"). Nazewnictwo tej właściwości ReleaseDate oznacza, że zwraca datę, a nie datę i godzinę. Nazwanie go ReleaseDateAsString dodatkowo wskazuje, że zwraca ciąg.
Bardziej elastyczne rozwiązanie polega na użyciu czegoś znanego jako konwerter wartości. Oto przykład tworzenia własnego konwertera wartości. Jeśli używasz języka C#, dodaj poniższy kod do pliku kodu źródłowego Recording.cs . Jeśli używasz języka C++/WinRT, dodaj nowy plik Midl (idl) do projektu, nazwany zgodnie z poniższym przykładem kodu C++/WinRT, skompiluj projekt, aby wygenerować StringFormatter.h i .cpp, dodaj te pliki do projektu, następnie wklej fragmenty kodu do nich. Dodaj także #include "StringFormatter.h" do MainPage.h.
public class StringFormatter : Windows.UI.Xaml.Data.IValueConverter
{
// This converts the value object to the string to display.
// This will work with most simple types.
public object Convert(object value, Type targetType,
object parameter, string language)
{
// Retrieve the format string and use it to format the value.
string formatString = parameter as string;
if (!string.IsNullOrEmpty(formatString))
{
return string.Format(formatString, value);
}
// If the format string is null or empty, simply
// call ToString() on the value.
return value.ToString();
}
// No need to implement converting back on a one-way binding
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
throw new NotImplementedException();
}
}
// pch.h
...
#include <winrt/Windows.Globalization.h>
// StringFormatter.idl
namespace Quickstart
{
runtimeclass StringFormatter : [default] Windows.UI.Xaml.Data.IValueConverter
{
StringFormatter();
}
}
// StringFormatter.h
#pragma once
#include "StringFormatter.g.h"
#include <sstream>
namespace winrt::Quickstart::implementation
{
struct StringFormatter : StringFormatterT<StringFormatter>
{
StringFormatter() = default;
Windows::Foundation::IInspectable Convert(Windows::Foundation::IInspectable const& value, Windows::UI::Xaml::Interop::TypeName const& targetType, Windows::Foundation::IInspectable const& parameter, hstring const& language);
Windows::Foundation::IInspectable ConvertBack(Windows::Foundation::IInspectable const& value, Windows::UI::Xaml::Interop::TypeName const& targetType, Windows::Foundation::IInspectable const& parameter, hstring const& language);
};
}
namespace winrt::Quickstart::factory_implementation
{
struct StringFormatter : StringFormatterT<StringFormatter, implementation::StringFormatter>
{
};
}
// StringFormatter.cpp
#include "pch.h"
#include "StringFormatter.h"
#include "StringFormatter.g.cpp"
namespace winrt::Quickstart::implementation
{
Windows::Foundation::IInspectable StringFormatter::Convert(Windows::Foundation::IInspectable const& value, Windows::UI::Xaml::Interop::TypeName const& /* targetType */, Windows::Foundation::IInspectable const& /* parameter */, hstring const& /* language */)
{
// Retrieve the value as a Calendar.
Windows::Globalization::Calendar valueAsCalendar{ value.as<Windows::Globalization::Calendar>() };
std::wstringstream wstringstream;
wstringstream << L"Released: ";
wstringstream << valueAsCalendar.MonthAsNumericString().c_str();
wstringstream << L"/" << valueAsCalendar.DayAsString().c_str();
wstringstream << L"/" << valueAsCalendar.YearAsString().c_str();
return winrt::box_value(hstring{ wstringstream.str().c_str() });
}
Windows::Foundation::IInspectable StringFormatter::ConvertBack(Windows::Foundation::IInspectable const& /* value */, Windows::UI::Xaml::Interop::TypeName const& /* targetType */, Windows::Foundation::IInspectable const& /* parameter */, hstring const& /* language */)
{
throw hresult_not_implemented();
}
}
...
public ref class StringFormatter sealed : Windows::UI::Xaml::Data::IValueConverter
{
public:
virtual Platform::Object^ Convert(Platform::Object^ value, TypeName targetType, Platform::Object^ parameter, Platform::String^ language)
{
// Retrieve the value as a Calendar.
Windows::Globalization::Calendar^ valueAsCalendar = dynamic_cast<Windows::Globalization::Calendar^>(value);
std::wstringstream wstringstream;
wstringstream << L"Released: ";
wstringstream << valueAsCalendar->MonthAsNumericString()->Data();
wstringstream << L"/" << valueAsCalendar->DayAsString()->Data();
wstringstream << L"/" << valueAsCalendar->YearAsString()->Data();
return ref new Platform::String(wstringstream.str().c_str());
}
// No need to implement converting back on a one-way binding
virtual Platform::Object^ ConvertBack(Platform::Object^ value, TypeName targetType, Platform::Object^ parameter, Platform::String^ language)
{
throw ref new Platform::NotImplementedException();
}
};
...
Uwaga / Notatka
W przypadku powyższego StringFormatter.idlkodu C++/WinRT użyjemy atrybutu domyślnego , aby zadeklarować element IValueConverter jako interfejs domyślny. Na liście StringFormatter ma tylko konstruktor i nie ma metod, więc dla niego nie jest generowany interfejs domyślny. Atrybut default jest optymalny, jeśli nie dodasz członków instancji do StringFormatter, ponieważ wywołanie metod IValueConverter nie będzie wymagane. Alternatywnie, można wywołać wygenerowanie domyślnego interfejsu IStringFormatter, robiąc to poprzez dodanie adnotacji do samej klasy uruchomieniowej przy użyciu atrybutu default_interface. Ta opcja jest optymalna, jeśli dodasz elementy członkowskie wystąpienia do StringFormatter, które są wywoływane częściej niż metody IValueConverter, ponieważ wtedy nie będzie potrzeby korzystania z QueryInterface do wywołania elementów członkowskich wystąpienia.
Teraz możemy dodać wystąpienie StringFormatter jako zasób strony i użyć go w powiązaniu z TextBlock, który wyświetla właściwość ReleaseDateTime.
<Page.Resources>
<local:StringFormatter x:Key="StringFormatterValueConverter"/>
</Page.Resources>
...
<TextBlock Text="{Binding ReleaseDateTime,
Converter={StaticResource StringFormatterValueConverter},
ConverterParameter=Released: \{0:d\}}"/>
...
Jak widać powyżej, na potrzeby elastyczności formatowania używamy znaczników do przekazywania ciągu formatu do konwertera za pomocą parametru konwertera. W przykładach kodu przedstawionych w tym temacie tylko konwerter wartości języka C# korzysta z tego parametru. Można jednak łatwo przekazać ciąg formatu w stylu C++ jako parametr konwertera i użyć go jako konwerter wartości za pomocą funkcji formatowania, takiej jak wprintf lub swprintf.
Oto wynik.
Uwaga / Notatka
Począwszy od systemu Windows 10 w wersji 1607, platforma XAML udostępnia wbudowany konwerter wartości logicznych do widoczności. Konwerter mapuje true na wartość wyliczenia Visibility.Visible i false na Visibility.Collapsed, dzięki czemu można powiązać właściwość Visibility z wartością Boolean bez konieczności tworzenia dodatkowego konwertera. Aby użyć wbudowanego konwertera, minimalna wersja docelowego zestawu SDK aplikacji musi być 14393 lub nowsza. Nie można jej używać, gdy aplikacja jest przeznaczona dla starszych wersji systemu Windows 10. Aby uzyskać więcej informacji na temat wersji docelowych, zobacz wersję adaptacyjną kodu.