در قسمتهای قبل یاد گرفتید که چطور View و View Model را در متد RegisterTypes در App.xaml.cs به یکدیگر وصل کرده و به آنها یک اسم دهید و با Navigation Service آنها را در حالتهای مختلف مثل Navigation Page و Master Detail و Popup و ... باز کنید و بین صفحات جابجا شوید.
در Viewها دو مورد را به صورت ابتدایی توضیح دادیم، Binding و Command. در این قسمت وارد جزئیات میشویم.
Command قطعهای کد در View Model است (عموما یک متد CSharp ای) که در View میتواند مورد استفاده قرار گیرد. برای این که کلیک بر روی یک دکمه، متد مربوطه در View Model را اجرا کند، آن را درون یک BitDelegateCommand قرار دادیم و به Command دکمه Bind کردیم.
میدانیم که Button، بجز کلیک، دارای Eventهای دیگری نیز هست. علاوه بر این، ممکن است بجای کلیک دکمه، بخواهیم زمانیکه روی یک عکس عمل Swipe انجام میشود، کاری در سمت View Model انجام شود.
برای حل این مشکل از Event To Command و Gesture Recognizer استفاده میکنیم. مورد اول همانطور که از ناماش پیداست، وظیفه آن اتصال بر قرار کردن بین یک Event و یک Command است و دومی نیز برای مسائلی چون Swipe - Tap - Pan - Pinch و ... بکار میرود.
در کد پایین که میتواند در HelloWorldView.xaml در پروژه XamApp نیز تست شود، یکبار Command دکمه را Bind کردهایم و یکبار Clicked Event آن را، که البته نتیجه یکی است! به جای نام Clicked میتوان نام هر یک از Eventها را نوشت. همچنین این کار بجز Button، بر روی باقی کنترلها نیز کار میکند.
<Button Command="{Binding IncreaseStepsCountCommand}" Text="Button with command binding" />
<Button Text="Button with event to command">
<Button.Behaviors>
<prismBehaviors:EventToCommandBehavior Command="{Binding IncreaseStepsCountCommand}" EventName="Clicked" />
</Button.Behaviors>
</Button>
همانطور که میبینید، EventToCommandBehavior که توسط Prism ارائه شدهاست، به Behaviorهای Button اضافه شدهاست. هر Behavior، رفتاری خاص را به کنترلهای شما اضافه میکند و امکان نوشتن Custom behavior نیز وجود دارد که از موضوع این بحث خارج است.
prismBehaviors در بالای فایل Xaml به شکل زیر تعریف شده است:
xmlns:prismBehaviors="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
این عمل معادل using نوشتن در بالای فایلهای CSharp است و میگوید که EventToCommandBehavior در کدام namespace و dll است. وقتی این کار را میکنید، میتوانید در Xaml به تمامی کلاسهای آن namespace دسترسی داشته باشید.
در همان مثال HelloWorldView.xaml، یک Label بود که تعداد زده شدن دکمه را نشان میداد. اگر بخواهیم وقتی روی آن Label به سمت چپ Swipe میکنیم نیز Command مربوطه اجرا شود، خواهیم داشت:
<Label Text="{Binding StepsCount, StringFormat='{}Button tapped {0} times!'}">
<Label.GestureRecognizers>
<SwipeGestureRecognizer Command="{Binding IncreaseStepsCountCommand}" Direction="Left" />
</Label.GestureRecognizers>
</Label>
در صورتیکه قصد تست بر روی Windows-UWP را دارید و صفحه نمایش لمسی ندارید، مطابق با آموزش
قسمت Windows همین سری آموزشی، بر روی Simulator خروجی بگیرید.
در مورد Command، مطالبی چون Command Parameter و Event Args Parameter Path و Event Args Converter باقی میمانند که در قسمتهای بعدی به آنها نیز خواهیم پرداخت. ولی به صورت کلی در نظر داشته باشید که جای منطق برنامه، در سمت View Model است و اگر در سناریویی نمیدانستید که چگونه در واکنش به تغییری در View، در سمت View Model کاری کنید، میتوانید همینجا سوال خود را بپرسید.
Binding نیز همان طور که تا الان متوجه شدهاید، باعث اتصال دو چیز به یک دیگر میشود. وقتی مینویسیم:
<Label Text="{Binding StepsCount, StringFormat='{}Button tapped {0} times!'}" />
داریم Text را که یک Property در Label در View است، به StepsCount که یک Property در View Model است، وصل میکنیم.
هر Binding یک Source دارد. به صورت پیش فرض، چون داریم از Prism استفاده میکنیم، تمامی Bindingها در View به صورت پیش فرض به View Model اشاره میکنند؛ مگر اینکه چیز دیگری بگوییم. برای مثال، اگر بخواهیم یک Label داشته باشیم که متن یک Entry را نمایش میدهد، هیچیک از این دو در View Model نیستند. پس در این Binding خاص، باید سورس را نیز مشخص کنیم:
<Entry x:Name="MyEntry" />
<Label Text="{Binding Text, Source={x:Reference MyEntry}}" />
این باعث میشود که Text، که یک Property در Label است، به Text، که یک Property در کنترلی با نام MyEntry است، وصل شود.
x:DataType
Bindingها به صورت پیش فرض، در زمان اجرا و با کمک Reflection کار میکنند؛ این مهم دو مسئله را ایجاد میکند:
1- اگر در View، نام Property مد نظر در View Model را به اشتباه بنویسید، تا زمانیکه برنامه را اجرا نکنید و وارد آن فرم نشوید، متوجه مشکل دار بودن ماجرا نمیشوید و وقتی در View Model نام یک Property را عوض میکنید، در سمت View در هنگام Build خطایی نمیگیرید.
2- Reflection چیز خوبی است و برخی سناریوها بدون آن خیلی سخت پیاده سازی میشوند؛ ولی Reflection سربار کارآیی دارد و بهتر است در صورت امکان از آن پرهیز شود.
برای حل این مشکل در Xamarin Forms امکانی به نام x:DataType معرفی شدهاست که در View میگویید که Source چه کلاسی است، که قاعدتا View Model است. برای این کار داریم:
<ContentPage
...
xmlns:vm="clr-namespace:XamApp.ViewModels"
x:DataType="vm:HelloWorldViewModel">
با این روش، هر دو مشکل فوق حل میشوند و هم خطای زمان Build برای Bindingهای اشتباه خواهیم داشت و هم بدون Reflection از Bindingها استفاده میکنیم که بهبود سرعت برنامه را در بر دارد.
INotifyPropertyChanged
در مثال XamApp، وقتی روی دکمه کلیک میکنیم، مقدار StepsCount را که یک Property در View Model است، یکی یکی افزایش میدهیم. این Property در View به Label بایند شدهاست. وقتی مقداری در View Model که سورس Binding ما محسوب میشود رخ میدهد، View چگونه خبر دار میشود؟
هر کلاسی که بخواهد در Binding به عنوان Source استفاده شود (منجمله View Model)، میتواند یک interface را پیاده سازی کند؛ به نام INotifyPropertyChanged. همچنین تعریف یک Property نیز باید به این شکل باشد:
private int _StepsCount;
public int StepsCount
{
get => _StepsCount;
set => SetProperty(ref _StepsCount, value);
}
طبیعتا این کد که در سناریوهایی میتواند حتی پیچیدهتر شود، جالب نیست و برای همین ما از
PropertyChanged.Fody استفاده کردهایم که در زمان Build، خودش همه مسائل را مدیریت میکند و کد فوق به سادگی میتواند به صورت زیر نوشته شود:
public int StepsCount { get; set; }
به
صفحه GitHub این کتابخانه مراجعه کنید و با سایر امکانات فوق العادهی آن آشنا شوید!
در Binding، موارد دیگری چون Value Converters و Relative Source نیز وجود دارند که در ادامه با آنها آشنا میشویم. اگر در Binding نیز مشکلی دارید، میتوانید همینجا نیز بپرسید.