سئوالات و مباحث WPF

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
سلامی مجدد استاد
خیلی ممنون از جواب پست قبلی تون .

استاد ، میگم که پست های 949 تا 963 را مرور کردم .
درباره ی مشکل Binding ای بود که توسط Converter بوجود میومد و در جریانش ، در پست 949 ، پروژه ی کوچیک SettingBinding را ایجاد کردم و شما هم در پست 952 ، پروژه ی BindingSample را دادید .

هر چند خلاصه ی صحبت های شما را در پست های 959 و پست 961 گفتم ، اما همونطور که اشاره کرده بودید به همون پست ها ، اندکی شاید اشکالاتی داشت .
حالا توی این پست میخوام اون مطالب را تصحیح کنم و البته به همراه یک نکته و این مطالبی که میگم را بی زحمت چک کنید ببینید درسته ؟ :

==========

اول ، یک نکته اینکه من در ILSpy و اینها که چک میکردم ، کلاس های ItemsControl و فرزندان شون ، از رویداد CollectionChanged (ئه اینترفیسِ INotifyCollectionChanged که توسط ObservableCollection<T> پیاده سازی شد) استفاده نمیکنن .
و متدی را به این رویداد CollectionChanged ، متصل نمیکنن .

حداقل من چیزی پیدا نکردم .

=========

در Binding ، فقط هر بار که شیِ اون پروپرتی عوض بشه ، شیِ بایندینگ ، به Binding Target Property اش اطلاع میده . در واقع شیِ Binding ، اشاره گر را برای Binding Target Property ، همون اشاره گرِ شی ای که Binding Source Property بهش اشاره میکنه ، در نظر میگیره (یا فرضا اگه اون شی ، استراکچر هست ، مقدارش را به همون تغییر میده) .

یعنی فرضا اگه پروپرتیِ HasGoodState را به عنوان Binding Source Property استفاده میکنیم ، اگه مقدارش تغییر کنه ، به Binding Target Property اش اطلاع و همون اشاره گر را براش در نظر میگیره .

برای کالکشن ها ، باز هم اگه فقط شیِ کالکشن عوض بشه (نه اینکه آیتمی بهش اضافه یا حذف بشه) ، شیِ بایندینگ ، به Binding Target Property اش بصورت اتوماتیک اطلاع میده . و مثل حالت قبلی ، به همون اشاره گری که Binding Source Property داره به شیِ کالکشن اشاره میکنه ، همون اشاره گر را به عنوان اشاره گر برای شیِ Binding Target Property در نظر میگیره .

یعنی اگه پروپرتیِ Drives را که به عنوان Binding Source Property استفاده میکنیم و به پروپرتیِ DrivesForView (به عنوان Binding Target Property) ، بایندینگ میکنیم ، در اینجا فقط اگه مقدار Drives تغییر کنه (نه اینکه عضوی را بهش اضافه و حذف کنیم) ، در این صورت فقط اتوماتیک به پروپرتیِ DrivesForView اطلاع و مقدارش را به همون اشاره گر تغییر میده .

اضافه شدن یا حذف شدن اعضا از یه کالکشن ، به Binding هیچ ارتباطی نداره و در این صورت ، Binding هیچ اطلاعی به Target Property اش نمیده .
Binding ، فقط در صورتی که مقدار پروپرتیِ Source Property اگه تغییر کنه ، اون مقدار یا اشاره گرِ اون مقدار را برای Target Property در نظر میگیره و تغییرش میده . (نه اینکه فرضا اگه Source Property مون ، یه کالکشن بود ، اعضای این کالکشن اگه تغییر کرد ، این کار را کنه یا فرضا اگه مقدار پروپرتی هایی که داخلِ Source Property مون هستن ، تغییر کرد ، این کار را کنه) .

همونطور که اشاره شد ، در واقع ، در این صورت (وقتی Binding میکنیم) ، هر دوی Binding Source Property و هم Binding Target Property ، از همون شی استفاده میکنن (مثل اینکه یک شی را توی یک متغییر بریزیم و بعد همون متغییر را توی متغییر دیگه ای بریزیم) .

بنابراین وقتی به یه پروپرتیِ (به عنوان Binding Target Property) ، یه کالکشن را بهش Binding کنیم ، فقط کافیه که یکبار کالکشنِ سورس مون ، مقداردهی بشه ، همون کالکشن ، به عنوان کالکشن Target مون در نظر گرفته میشه . پس دیگه مهم هم نیست که دیگه با هر بار اضافه شدن آیتم به کالکشنِ سورس مون ، خبری به Binding یا Target داده بشه .
حالا به کالکشن Source مون هم که هر تغییری بدیم و اعضایی را حذف یا اضافه کنیم ، یعنی به همون کالکشنِ Target مون همون تغییرات را داده بودیم (چون شیِ کالکشنِ هر دو پروپرتی ، یکی هستن) .

یعنی شکل زیر ، بیانگر توصیفات بالا هست :


1.jpg


==================


حالا مشکل این پروژه ام (پروژه ی BindingSetting در پست 949) اینه که برای این Binding ، از Converter بصورتی استفاده کردم که شیِ جدیدی در Converter برای Binding ام ساختم .

Target Property هم اگه از Converter ای استفاده کنیم ، همیشه ، شی ای که Converter در اختیارش قرار میده را استفاده میکنه و به این شی ، Binding انجام میده (چه این شی ای که Converter در اختیارش قرار میده ، همون شی ای باشه که Source Property بهش اشاره میکنه ، یا اینکه شی دیگه ای باشه) (یعنی در این صورت ، Target Property مون مستقیما کاری به شی Source Property نداره و بهش Binding نمیشه) .


در این پروژه ، پروپرتی Drives که از نوع InternalHard_DriveCollection و این نوع هم همون ObservableCollection<Drive> هست ، به عنوان Source Property مون هست .
پروپرتی DrivesForView هم که از نوع ObservableCollection<DriveForView> هست ، به عنوان Target Property مون هست .
در این Binding هم از Converter استفاده کردیم .

وقتی Drives را به DrivesForView مون Binding کردیم ، زمانی که به پروپرتیِ Drives مقدار (شی ای از InternalHard_DriveCollection) دادیم ، این شی توسط شیِ Binding ، (به عنوان پارامتر value ، به متدِ) Convert مون اطلاع میده .

در این لحظه ، متد Convert ، از این شی ای که Drives بهش اشاره میکنه ، هیچ استفاده ای نمیکنه و یک شیِ دیگه ای (از نوع ObservableCollection<DriveForView>) میسازه و Target Property مون دیگه از این شی استفاده میکنه .

همونطور هم که قبلا اشاره شد ، اگه از Converter استفاده کنیم ، Target Property مون (در اینجا DrivesForView) ،در واقع به شی ای که Converter مون به عنوان خروجی میده ، Binding میشه (حالا Converter مون میخواد چه همون شی ای که در Source Property بهش اشاره میشه را تحویل بده یا یه شی دیگه را تحویل بده) (یعنی در این صورت ، Target Property مون مستقیما کاری به شی Source Property نداره) .

و چون شی ای که در متد Convert تحویل میده ، شی ای متفاوت از شی ای هست که Source Property مون بهش اشاره میکنه ، پس هر وقت اگه به اعضای Source Property مون ، عضوی اضافه یا کم بشه ، شی ای که در خروجیِ Converter ازش استفاده میکنیم
(شیِ از نوع ObservableCollection<DriveForView>) ، مطلع نمیشه تا به اعضای خودش هم چیزی اضافه یا حذف یا ویرایش کنه (چون وظیفه ی Binding همونطور که گفته شد ، فقط وقتی هست که مقدار پروپرتی تغییر کنه ، نه اینکه اعضای کالکشن تغییر کنه و ...) و چون این دو شی (شی در خروجیِ Convert و شی Source Property) یک شی هم نیستن (و دو شی مجزا هستن) ، با اضافه شدن اعضا به شی Source Property ، مشخص و بدیهی هست که شی دیگری هم تغییری نمیکنه .

راه حل هم اینه که در Converter ، هندلری به رویداد CollectionChanged ئه شیِ Source Property مون متصل کنیم و طبق حذف و اضافه شدنِ اعضا به این شی ، اون عضو را در شیِ خروجیِ Converter هم حذف یا اضافه کنیم .



SettingBinding.jpg


درسته؟
ببخشید زیاد شد .
تشکر استاد :rose:
 

the_king

مدیرکل انجمن
اول ، یک نکته اینکه من در ILSpy و اینها که چک میکردم ، کلاس های ItemsControl و فرزندان شون ، از رویداد CollectionChanged (ئه اینترفیسِ INotifyCollectionChanged که توسط ObservableCollection<T> پیاده سازی شد) استفاده نمیکنن .
و متدی را به این رویداد CollectionChanged ، متصل نمیکنن .

حداقل من چیزی پیدا نکردم .
مثلا DataGrid که ItemsControl ئه در متد سازنده اش columns.CollectionChanged_ رو متصل می کنه به OnColumnsChanged تا متوجه بشه که Columns اش کی تغییر می کنه و سریع واکنش نشون بده.
در Binding ، فقط هر بار که شیِ اون پروپرتی عوض بشه ، شیِ بایندینگ ، به Binding Target Property اش اطلاع میده . در واقع شیِ Binding ، اشاره گر را برای Binding Target Property ، همون اشاره گرِ شی ای که Binding Source Property بهش اشاره میکنه ، در نظر میگیره (یا فرضا اگه اون شی ، استراکچر هست ، مقدارش را به همون تغییر میده) .
بله.
یعنی فرضا اگه پروپرتیِ HasGoodState را به عنوان Binding Source Property استفاده میکنیم ، اگه مقدارش تغییر کنه ، به Binding Target Property اش اطلاع و همون اشاره گر را براش در نظر میگیره .

برای کالکشن ها ، باز هم اگه فقط شیِ کالکشن عوض بشه (نه اینکه آیتمی بهش اضافه یا حذف بشه) ، شیِ بایندینگ ، به Binding Target Property اش بصورت اتوماتیک اطلاع میده . و مثل حالت قبلی ، به همون اشاره گری که Binding Source Property داره به شیِ کالکشن اشاره میکنه ، همون اشاره گر را به عنوان اشاره گر برای شیِ Binding Target Property در نظر میگیره .
بله.
یعنی اگه پروپرتیِ Drives را که به عنوان Binding Source Property استفاده میکنیم و به پروپرتیِ DrivesForView (به عنوان Binding Target Property) ، بایندینگ میکنیم ، در اینجا فقط اگه مقدار Drives تغییر کنه (نه اینکه عضوی را بهش اضافه و حذف کنیم) ، در این صورت فقط اتوماتیک به پروپرتیِ DrivesForView اطلاع و مقدارش را به همون اشاره گر تغییر میده .
بله.
اضافه شدن یا حذف شدن اعضا از یه کالکشن ، به Binding هیچ ارتباطی نداره و در این صورت ، Binding هیچ اطلاعی به Target Property اش نمیده .
بله.
Binding ، فقط در صورتی که مقدار پروپرتیِ Source Property اگه تغییر کنه ، اون مقدار یا اشاره گرِ اون مقدار را برای Target Property در نظر میگیره و تغییرش میده . (نه اینکه فرضا اگه Source Property مون ، یه کالکشن بود ، اعضای این کالکشن اگه تغییر کرد ، این کار را کنه یا فرضا اگه مقدار پروپرتی هایی که داخلِ Source Property مون هستن ، تغییر کرد ، این کار را کنه) .
بله.
همونطور که اشاره شد ، در واقع ، در این صورت (وقتی Binding میکنیم) ، هر دوی Binding Source Property و هم Binding Target Property ، از همون شی استفاده میکنن (مثل اینکه یک شی را توی یک متغییر بریزیم و بعد همون متغییر را توی متغییر دیگه ای بریزیم) .
این ماهیت Binding نیست، کلی نتیجه نگیرید. شما فرض می کنید که مقدار a به مشخصه X تحویل داده شد و در نتیجه Binding مطلع شد که مقدار X به a تغییر کرده و در نتیجه مقدار a رو به مشخصه Y انتقال میده و Y مقدار جدید a رو بدون دستکاری خواهد داشت.
شما یک شرایطی رو توصیف می کنید که اینطور به نظر میاد ولی ممکنه همواره هم این شرایط نباشه، به چند دلیل. مثلا مشخصه مقداری رو get و set می کنه که روال شون جدا است، الزاما یکسان نیستند. ثانیا Binding یک حالت خاص و ثابت نداره، ممکنه Binding ای بر اساس روالی که برایش نوشته شده مقداری رو انتقال بده که الزاما مقدار مبدا نیست. بر اساس تغییرات مقدار در مبدا عمل می کنه، اما اینکه چه مقداری رو انتقال میده به کد و روالش بستگی داره، همیشه و الزاما دقیقا اون مقدار مبدا نیست که منتقل میشه. Binding زمانی که مبدا اعلام کرد که مقدارش تغییر کرده، اقدام به انتقال مقدار جدید می کنه، اما چه مقداری؟ مقداری که مبداء تمایل داره اعلام کنه یا Binding مایل به انتقالش هست، نه الزاما مقداری که به مشخصه مبدا تحویل داده شده بود.
بنابراین وقتی به یه پروپرتیِ (به عنوان Binding Target Property) ، یه کالکشن را بهش Binding کنیم ، فقط کافیه که یکبار کالکشنِ سورس مون ، مقداردهی بشه ، همون کالکشن ، به عنوان کالکشن Target مون در نظر گرفته میشه . پس دیگه مهم هم نیست که دیگه با هر بار اضافه شدن آیتم به کالکشنِ سورس مون ، خبری به Binding یا Target داده بشه .
مهم بودن و نبودنش به نیت برنامه نویس بستگی داره.
حالا به کالکشن Source مون هم که هر تغییری بدیم و اعضایی را حذف یا اضافه کنیم ، یعنی به همون کالکشنِ Target مون همون تغییرات را داده بودیم (چون شیِ کالکشنِ هر دو پروپرتی ، یکی هستن) .
بله.
راه حل هم اینه که در Converter ، هندلری به رویداد CollectionChanged ئه شیِ Source Property مون متصل کنیم و طبق حذف و اضافه شدنِ اعضا به این شی ، اون عضو را در شیِ خروجیِ Converter هم حذف یا اضافه کنیم .


درسته؟
درست که بالاخره یک راه حل ئه، ممکنه بعدا راه حل بهتری برایش پیدا کنید یا اصلا تغییری در اساس کار بدهید که نیازی به این اینکار نباشه.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
ربط این مساله که از XAML استفاده شده به جدا بودن UI از منطق تجاری در اینه که در WPF که قرار بوده واسط کاربری مستقل از منطق تجاری باشه و منطق تجاری رو با زبانی مثل #C پیاده سازی می کنیم، اما با #C میشد واسط کاربری رو هم توصیف کرد؟ خیر. همچین کاری رو نمیتونه انجام بده چون زبان توصیفی نیست. شما با #C می توانید یک چیزهایی رو توصیف کنید، مثلا Interface ای رو مثل ICollection رو توصیف می کنید، اما اون واسط کاربری نیست، واسط ای است که برای ارتباط بین اشیاء بکار می برید.

برای توصیف کردن واسط کاربری باید از زبان مناسب توصیف واسط کاربری استفاده کرد، بصورت متعارف این یعنی زبان های نشانه گذاری، مثلا XAML، نه یک زبان برنامه نویسی.
#C یک Programming Language ئه، نه Markup Language.

نمیشه واسط کاربری روی با #C توصیف کرد، چیزی که شما با #C برای ساختن واسط کاربری می نویسید توصیف نیست، کدی برنامه ای است که با اجرا شدنش اشیاء ای رو ایجاد می کنه، توصیف نمی کنه. همونطور که با Markup language ای مثل XAML نمیشه پیچیدگی های منطق تجاری رو پیاده سازی کرد. برای همینه که HTML که یک Markup Language ئه کافی نیست و نیاز به javascript ای پیدا می کنیم که یک زبان برنامه نویسی است.


سلامی مجدد استاد .
خیلی ممنون از جواب پست قبلی تون :rose:


بله ، دقیقا درست میگید .
سی شارپ ، زبان توصیفی نیست که ظاهر چیزی (یا ظاهر کنترلی) را توصیف کنه .
اما به قول شما ، با اجرا شدنش ، باعث میشه اشیایی را ایجاد میکنه .

همین اینکه باعث ایجاد شی ای میتونه بشه و بر این اساس ، ظاهر و شی ای (از کنترل و ...) را ایجاد کنه ، یعنی میشه باهاش طراحی ظاهر را انجام داد ، هر چند سی شارپ برای این کار طراحی نشده باشه که باعث میشه تا کار طراحی و توصیف ظاهر کنترل (و کلا UI) ، خیلی سخت تر از زبان های توصیفی (مثل xaml) انجام بشه .

در واقع ، بحث روی سختی و آسونیِ طراحی ظاهر (UI) در سی شارپ و xaml هست .
نه اینکه فلان شکلِ UI (یا فلان شکل از کنترل) را در سی شارپ نمیشه با اجرای کدش ساخت اما در xaml میشه .

در واقع شاید این طور بشه مثال زد که قبلا هم بارها گفته بودین که قاشق ذاتا برای غذا خوردن و بیل برای جمع کردن شِن طراحی شدن . اما این دلیل نمیشه که به هیچ عنوان و اصلا با قاشق نمیتوان شن جمع کرد و به هیچ وجه و اصلا با بیل نمیتوان غذا خورد .
تونستن میشه اما بحث راحت تر و سریعتر انجام شدن کار هست چون هر کدوم برای کاری طراحی شدن .

=============

با توجه به نظرات شما و مطالب دیگه ای که خوندم (هر چند در اون مطالب ، صریحا به این نکته اشاره نشد) ، اما جمع بندی من (که ممکنه غلط هم باشه اما طبق این چیزها و فکرهایی که کردم ، بعیده که حداقل ، دور از واقعیت باشه) اینه که ، اینکه xaml و زبان توصیفی در wpf استفاده میشه ، حداقل ، به تنهایی باعث نمیشه که بصورت همزمان بشه هم UI و هم Model (و منطق تجاری) را ایجاد کرد .

بلکه صرفا باعث میشه کار جداسازیِ UI از Model (یا منطق تجاری) ، آسون تر انجام بشه (یعنی بحث بر سرِ انجام شدن یا نشدنِ جداسازی و همزمان کار کردنِ باهاشون نیست . بلکه بر سر آسون تر و سخت تر انجام شدنِ این جداسازی و همزمان کار کردن باهاشون هست) . اما این ، همه ی ماجرا نیست .

چون اگه به تنهایی xaml باعث بشه که در wpf ، همزمان بشه که هم UI را طراحی کرد و هم Model را نوشت ، خوب در windows form هم که یه گروهی دارن پروژه مینویسن ، چند نفر ازش جدا میشن و با استفاده از همون زبان سی شارپ ، رابط کاربری (UI) را جداگانه و همزمان طراحی میکنن و مینویسن (بدون اینکه اختلالی در کار کسایی که Model یا منطق تجاری را دارن مینویسن ، ایجاد کنن منتها طراحی رابط کاربری با سی شارپ ، صرفا سخت تر هست و برای این کار اساسا ساخته نشد (نه اینکه اصلا نتوان باهاش این کار را کرد) چون همونطور که توضیح داده بودید ، اساسا سی شارپ زبان توصیفی نیست .

اما ماجرای دیگه ای که حتی تاثیر گذارتر از استفاده از xaml هست برای اینکه کار جداسازی ، واضح تر و صریح تر (یا بصورت cleanly) و در نتیجه ، همزمان طراحی کردنِ UI و نوشتنِ Model (و منطق تجاری) ، آسون تر انجام بگیره و تاثیر بیشتری هم داره ، استفاده از مخصوصا معماری MVVM هست .

هر چند ، استفاده از هر معماری 3 لایه ی دیگه ای (مثل مدلِ Presentation - Business Logic - Database) ، هم خیلی کمک کننده در این جداسازی هست اما معماری MVVM بخاطر داشتن لایه ی ViewModel ، بهتر از بقیه ی مدل های 3 لایه ی دیگه ، برای جداسازی و در نتیجه همزمان نوشتنِ Model و طراحی UI (یا View) هست .

===============

و نهایتا نتیجه گیری ام اینه که در wpf :
1 - مخصوصا از معماری MVVM استفاده بشه
(هر چند windows form هم میتونه از این معماری استفاده کنه اما بخاطر موتور Binding که انگار در این پلتفرم ضعیف تر هست و اگه از Binding استفاده بشه ، عملکردش در wpf بهتر میشه) ؛
2 - و همچنین چون از xaml برای توصیف رابط کاربری استفاده میکنه

این دو عامل بالا در wpf باعث میشن که تا حدودی ، کار جدا سازیِ UI و Model (یا منطق تجاری) ، آسون تر انجام بشه و در نتیجه همزمان طراحی کردنِ UI و Model (یا منطق تجاری) در wpf ، حدودا راحت تر از windows form باشه .

در نتیجه ، به نظرم ، بحث روی آسونتر یا سخت تر انجام شدنِ اون کار در wpf
(بخاطر اون دو مورد که در بالا گفتم) هست . نه اینکه اساسا قطعا در wpf بشه ، همزمان اون UI را طراحی کرد و همچنین Model (یا منطق تجاری) را نوشت اما در windows form ، اساسا این همزمانی را نشه انجام داد .

استاد ، این جمله که "wpf اجازه میده که برنامه نویس و طراح ، کارشون را مجزا و همزمان انجام بدن" ، را یکی از آموزش دهنده ها در یکی از آموزش های udemy بصورت انگلیسی گفت . هر چند علاوه بر این ، ممکنه در سایتی هم دیده باشم .

به نظرتون این جمله ، دقیق هست؟

و به نظرم این چیزی که در پست 964 اشاره کردم که اون آموزش دهنده ی انگلیسی گفت ، چندان درست نیست .
چون بحث روی آسونتر یا سخت تر انجام شدن کار هست (که مجموعه ی wpf برای اون هدف طراحی شد). نه اینکه اساسا یه کاری در wpf قابلیت انجام اون کار را داشته باشه و در windows form نداشته باشه .
که در لینک انگلیسی ای که در همون پست دادم ، به این قضیه اشاره کردم .

=====================

موضوع بعدی اینکه استاد وقتی از موتور Binding صحبت میکنیم ، در واقع از چی داریم صحبت میکنیم؟
منظورشون صرفا کلاس Binding هست یا چیز دیگه ای هست؟
یا در واقع چی میشه که به بعضی چیزها مثل رندر و Binding ، میگن موتور رندر و موتور Binding اما به خیلی چیزهای دیگه نمیگن؟


اما به نظرم وقتی مقدار Binding Source Property (ای که INotifyPropertyChanged برای کلاسش پیاده سازی شد) ، تغییر کرد ، وظیفه ی موتور و شی Binding هست که از این تغییر مطلع بشه (حالا اینکه به رویدادِ اون INotifyPropertyChanged متصل کنه یا نه ، نمیدونم) و نهایتا مقدار Binding Target Property را به همون اشاره گر یا مقدار ، تغییر بده (البته قضیه ی Converter و اینها را در Binding میدونم) .

ببخشید استاد زیاد شد .
تشکر :rose:
 
آخرین ویرایش:

the_king

مدیرکل انجمن
موضوع بعدی اینکه استاد وقتی از موتور Binding صحبت میکنیم ، در واقع از چی داریم صحبت میکنیم؟
منظورشون صرفا کلاس Binding هست یا چیز دیگه ای هست؟
نه. فقط کلاس Binding نیست. فرض کنید که در زمان طراحی WPF اون مکانیسم Binding نبود. حالا شما اگر بعدا کلاس های Binding رو اضافه می کردید یک جای کار می لنگید، چون XAML و مشخصه های کنترل های WPF و سایر کلاس ها برای استفاده از اون Binding روال مشخصی نداشتند، تعامل با Binding به شکلی که الان هست عمل نمی کرد.
ماهیت Binding چیزی فراتر از کلاس Binding و وارثین اونه. پلتفرم WPF طوری طراحی شده که از اون Binding استفاده کنه، فقط کلاس Binding به تنهایی نمیتونسته کافی باشه. برای همین وقتی از Binding صحبت میشه مکانیسمی رو در نظر میگیره که در کلاس های Binding و سایر کلاس هایی است که این قابلیت رو دارند که از Binding استفاده کنند.
یا در واقع چی میشه که به بعضی چیزها مثل رندر و Binding ، میگن موتور رندر و موتور Binding اما به خیلی چیزهای دیگه نمیگن؟
engine یا موتور در نرم افزار به کمپوننت ای میگن که در هسته اون نرم افزار قابلیت کلیدی اش رو اداره میکنه. مثلا یک بازی کامپیوتری موتوری داره که بازی سازی در اون انجام شده، یک نرم افزار گرافیکی موتوری داره که دستورات گرافیک در اون رندر شده و تصویر ایجاد شده، یک نرم افزاری پخش صوتی موتوری داره که فایل های صوتی در اون کدگشایی و به صوت تبدیل میشه. یک مرورگر اینترنتی موتوری داره که در اون صفحات وب تفسیر و ترسیم میشه.
حالا اگر شما بخواهید در نرم افزارتون یک قابلیت کلیدی مثل اجرای دستورات صوتی رو اضافه کنید سراغ کمپوننت هایی می روید که بصورت آنلاین یا آفلاین این سرویس رو به شما می دهند، اون کمپوننت ها موتور هایی هستند که فرمان صوتی مشخصی رو شناسایی می کنند.
موتور یک کمپوننت ئه، به تنهایی کار نمی کنه، در خدمت نرم افزار قرار میگیره تا یک وظیفه کلیدی نرم افزار رو بر عهده بگیره.
طبعا این موتور API داره، میتونه به صورت سرویس در اختیار نرم افزار های متعددی قرار بگیره، میتونه اونقدر متمایز و استفاده گسترده داشته باشه که برای خودش یک پلتفرم با استاندارد شناخته شده ای بشه یا اونقدر انحصاری باشه که فقط موتور داخلی فلان نرم افزار خاص بشه.

اما به نظرم وقتی مقدار Binding Source Property (ای که INotifyPropertyChanged برای کلاسش پیاده سازی شد) ، تغییر کرد ، وظیفه ی موتور و شی Binding هست که از این تغییر مطلع بشه (حالا اینکه به رویدادِ اون INotifyPropertyChanged متصل کنه یا نه ، نمیدونم) و نهایتا مقدار Binding Target Property را به همون اشاره گر یا مقدار ، تغییر بده (البته قضیه ی Converter و اینها را در Binding میدونم) .
متوجه نشدم که این در پاسخ به کدوم بخش از گفتگو است.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
خیلی خیلی ازتون ممنونم استاد :rose:
اگه هزاران بار ازتون تشکر کنم ، ذره ای از لطف این همه سال هاتون که برام کشیدین ، جبران نمیشه . :rose:
ای کاش دوباره سریعتر برگردین :( :cry:

مطلب زیر را برای یادآوری میذارم :

------

سلام
داستان این هست که DataGrid که 2 حالتِ در حال ویرایش و غیر ویرایش (فقط نمایش اطلاعات) داره .
در حال ویرایش ، زمانی هست که کاربر داره مقداری را وارد یا عوض میکنه .
بصورت پیش فرض ، وقتی کاربر ، شروع به ویرایش میکنه ، یه کنترل TextBox درونِ اون سلول ایجاد میشه که وقتی از این حالت خارج بشه (با زدن کلید Enter یا هر چیز دیگه) ، دیگه این TextBox وجود نداره که مقدارش در دسترس باشه .

مقدارِ این TextBox که شامل اطلاعاتی که کاربر ، ویرایش کرد ، هست ، نهایتا فقط تا زمانی که رویدادِ CellEditEnding ئه DataGrid اجرا بشه (توسط متغییرِ رویدادیِ e.EditingElement که بصورت پیش فرض ، اگه خودمون ControlTemplate ای ندیم ، از نوع TextBox هست) ، در دسترس هست . یعنی نهایتا تا همون موقع و همون رویداد میتونیم متوجه بشیم که کاربر ، چه اطلاعاتی را وارد و یا ویرایش کرد .

یعنی حتی در رویداد RowEditEnding هم این اطلاعات دیگه در دسترس مون نیست .
CellEditEnding ، زمانی هست که کاربر ، یک سلول را ویرایش کرد و کارش تمام شد (با unfocuse شدنِ سلول و هر جور دیگه ای) .
RowEditEnding هم زمانی که ویرایش یک سطر ، به اتمام رسید .
هر دو رویداد ، قبل از اجرای متد Validate برای اعتبارسنجی ، اجرا میشن .

بنابراین یکی از راهکارها اینه که تا زمانی که درون رویداد CellEditEnding هستیم ، چون به متد Validate ، مقدارِ DataGrid.Items.CurrentItem مون (که در مثال بالا همون شیِ Person هست) ، ارسال میشه (به عنوان مقدار پارامتر value که از نوع BindingGroup و عضوِ Items[0] اش) ، پس اعضای این CurrentItem را به مقداری که کاربر درون اون TextBox قرار میده ،تغییر بدیم .

------

یا بهتر اینکه (برای اینکه پروپرتیِ CurrentItem تغییری نکنه چون ممکنه یه جای دیگه مشکل ساز بشه و ندونیم) ، در این رویدادِ CellEditEnding ، اطلاعات را بگیریم و ذخیره کنیم و در زمانی که در رویداد RowEditEnding هستیم ، اون را برای متغییر یا پروپرتی استاتیک ای که در کلاس DataGridItemsSource_Persons_ValidationRule تعریف کردیم ، بفرستیم و بعد ، مقدار اون عضو استاتیک را در متد Validate ، زمانی که عملیات با موفقیت انجام شد (یعنی سر آخر) قبل از اینکه از متد خارج بشیم ، null کنیم .


البته برای اینکه MVVM نقض نشه ، میشه در کلاسی که از ValidationRule ارث میبره (در اینجا کلاس DataGridItemsSource_Persons_ValidationRule) ، متدهای استاتیکی ساخت و وظایف مقداردهی ها را توسط این متدها به لایه ی ViewModel منتقل کرد (چون این کلاس در لایه ی ViewModel هست) ، پس روش زیر پیشنهاد میشه :

در MainWindow.xaml :

XML:
<Window x:Class="WPF_Practice_2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Practice_2"
        xmlns:localClasses="clr-namespace:WPF_Practice_2.Classes"
        xmlns:bindingValidations="clr-namespace:WPF_Practice_2.Classes.BindingValidations"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.Resources>

        <localClasses:ViewModel x:Key="ViewModelKey"/>

        <CollectionViewSource x:Key="PersonsCollectionViewSourceKey"
            Source="{Binding
            Source={StaticResource ViewModelKey},
            Path=PhoneBook.Persons}"/>
            
        <ControlTemplate TargetType="{x:Type Control}"
        x:Key="PersonValidationRulesControlTemplateKey">
            <Grid Margin="0,-2,0,-2" Background="Transparent"
            ToolTip="{Binding RelativeSource={RelativeSource FindAncestor,
            AncestorType={x:Type DataGridRow}},
            Path=(Validation.Errors)[0].ErrorContent}">

                <Ellipse StrokeThickness="0" Fill="Red" Width="{TemplateBinding FontSize}"
                Height="{TemplateBinding FontSize}"/>
                <TextBlock Text="!" FontSize="{TemplateBinding FontSize}" FontWeight="Bold"
                Foreground="White" HorizontalAlignment="Center"  />

            </Grid>
        </ControlTemplate>

    </Window.Resources>
    
    
    
        <Grid DataContext="{Binding
    Source={StaticResource PersonsCollectionViewSourceKey}}">

        <DataGrid Name="PhoneBookDataGrid" HorizontalAlignment="Center" Height="271"
        Margin="0,153,0,0" VerticalAlignment="Top" Width="780"
        AutoGenerateColumns="False"
        ItemsSource="{Binding ValidatesOnExceptions=True}"
        RowValidationErrorTemplate="{StaticResource PersonValidationRulesControlTemplateKey}"
        CellEditEnding="PhoneBookDataGrid_OnCellEditEnding"
        RowEditEnding="PhoneBookDataGrid_OnRowEditEnding"
        AlternationCount="{Binding Count}">

            <DataGrid.RowValidationRules>
                <bindingValidations:DataGridItemsSource_Persons_ValidationRule/>
            </DataGrid.RowValidationRules>

            <DataGrid.Columns>
                <DataGridTextColumn Header="ردیف" IsReadOnly="True"
                Binding="{Binding AlternationIndex,
                RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>

                <DataGridTextColumn Header="شناسه"
                Binding="{Binding Path=PersonID, ValidatesOnExceptions=True}"/>

                <DataGridTextColumn Header="نام"
                Binding="{Binding Path=FirstName, ValidatesOnExceptions=True}"/>

                <DataGridTextColumn Header="نام خانوادگی"
                Binding="{Binding Path=LastName, ValidatesOnExceptions=True}"/>

                <DataGridTextColumn Header="شماره تماس"
                Binding="{Binding Path=PhoneNumber, ValidatesOnExceptions=True}"/>

                <DataGridTextColumn Header="کد ملی"
                Binding="{Binding Path=NationalCode, ValidatesOnExceptions=True}"/>
            </DataGrid.Columns>

        </DataGrid>
        
            </Grid>
</Window>



در MainWindow.xaml.cs :

C#:
    public partial class MainWindow : Window
    {
        /// <summary>
        /// برای فلگ کردن اینکه وقتی که رویداد RowEditEnding میرسد ، آیا قبل از آن ، سلول مربوط به آن سطر جاری تغییر کرد که باعث اجرای این رویداد شد
        /// تا برای اعتبار سنجی ارسال شود یا نه .
        /// </summary>
        private bool doesCurrentRowCellChangeFlag = false;




        private void PhoneBookDataGrid_OnCellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
        {
            if (this.doesCurrentRowCellChangeFlag == false)
                this.doesCurrentRowCellChangeFlag = true;

            DataGridItemsSource_Persons_ValidationRule.CellEditEnding(this.PhoneBookDataGrid.Items.CurrentItem,
                e.Column.SortMemberPath, ((TextBox) e.EditingElement).Text);
        }


        private void PhoneBookDataGrid_OnRowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
        {
            /// ممکنه این رویداد ، علاوه بر زمان ویرایش سلول ، مجددا در زمانی که سلول را که ویرایش کرده بودیم و فوکوس را روی یه کنترل دیگه میخوایم منتقل کنیم ،
            /// مجددا این رویداد اجرا بشه . این شرط ، برای جلوگیری از اجرای این رویداد در اون موقع هست تا عمل binding را به model منتقل نکنه .
            if (this.doesCurrentRowCellChangeFlag == false)
            {
                e.Cancel = true;
                return;
            }

            DataGridItemsSource_Persons_ValidationRule.RowEditEnding();
            this.doesCurrentRowCellChangeFlag = false;
        }


    }

ادامه ...
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
ادامه ی پست قبلی :


در کلاس DataGridItemsSource_Persons_ValidationRule که در لایه ی ViewModel باشه (نوع کلاس Person ، در لایه ی Model تعریف شده) :

C#:
    public  class DataGridItemsSource_Persons_ValidationRule : ValidationRule
    {
        private static Person editValidatePerson = null;

        private static Person tempEditValidatePerson = null;




        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            /// اگر مقدار EditValidatePerson برابر با null بود ، پس یعنی اعتبار سنجی ، یکطرفه و از طرف Model داره به View منتقل میشه ، پس همه را معتبر بزنه .
            if (editValidatePerson == null)
                return ValidationResult.ValidResult;

            if (editValidatePerson.PersonID < 0)
                return new ValidationResult(false, "شناسه باید عددی مثبت باشد");

            /// فقط در این قسمت ، یعنی در صورت موفقیت آمیز بودن عمل ثبت ، null میکنیم چون اگر موفقیت آمیز نباشد ، اطلاعات پاک نشود
            /// و امکان ویرایشِ مجددِ آخرین اطلاعاتِ وارد شده ، وجود داشته باشد .
            tempEditValidatePerson = null;
            editValidatePerson = null;
            return ValidationResult.ValidResult;
        }



        /// <summary>
        /// نوع پارامتر person باید از نوع Person باشد .
        /// برای این اینجا obnect تعریف شده چون برای ارسال از View به اینجا ، نیاز به تبدیل نباشد تا MVVM نقض شود .
        /// </summary>
        public static void CellEditEnding(object currentRowPerson, string editedPersonItemPropertyName, string editedPersonItemValue)
        {
            Person tempPerson = currentRowPerson as Person;
            if (tempPerson == null)
                throw new Exception("person");

            if (tempEditValidatePerson == null)
            {
                tempEditValidatePerson = new Person(tempPerson.PersonID, tempPerson.FirstName, tempPerson.LastName,
                    tempPerson.PhoneNumber, tempPerson.NationalCode);
            }

            if (editedPersonItemPropertyName == "PersonID")
            {
                int personID = -1;
                if (int.TryParse(editedPersonItemValue, out personID) == true)
                    tempEditValidatePerson.PersonID = personID;
            }
            else if (editedPersonItemPropertyName == "FirstName")
                tempEditValidatePerson.FirstName = editedPersonItemValue;
            else if (editedPersonItemPropertyName == "LastName")
                tempEditValidatePerson.LastName = editedPersonItemValue;
            else if (editedPersonItemPropertyName == "PhoneNumber")
            {
                int phoneNumber = -1;
                if (int.TryParse(editedPersonItemValue, out phoneNumber) == true)
                    tempEditValidatePerson.PhoneNumber = phoneNumber;
            }
            else if (editedPersonItemPropertyName == "NationalCode")
                tempEditValidatePerson.NationalCode = editedPersonItemValue;
        }


        public static void RowEditEnding()
        {
            editValidatePerson = tempEditValidatePerson;
        }


    }


ولی واقعا این بدیهی هست که باید DataGrid جوری طراحی میشد که در متد Validate ، اطلاعاتی که قراره ثبت بشه را در اختیارمون بذارن .
این چه طراحی ای بود ، تعجب میکنم!!
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
سلام
این را هم برای یادآوری ام در آینده میذارم :

حل مشکل شماره ردیف سطرها در حالت فعال بودن مجازی سازی :

سلام
البته ، در این روش (در روش پست شماره 9 و 3) ، همونطور که در پست اول تون هم گفته بودین ، این مشکل وجود داره که وقتی اسکرول میشه ، ممکنه ترتیب شون به هم بریزه .

============

دلیل :
این ، بخاطر این هست که Virtualization فعال هست و احتمالا هم نوع مجازی سازی روی حالت Recycle تنظیم شده .

مجازی سازی ، چیزی هست که چون به ازای هر آیتم ، کنترل بصری ایجاد میشه و مخصوصا اگه تعداد آیتم ها هر چی زیادتر باشه ، ساختن کنترل بصری هم اولا بسته به همون تعداد ، زمان خیلی بیشتر و همچنین حجم حافظه ی خیلی زیادی را مصرف میکنه (نسبت به داده ها منظورم هست) ، پس در این حالت ، موقع لود ، مخصوصا هر چی تعداد آیتم ها بیشتر باشه ، زمان لود و مصرف حافظه را هم به همون نسبت طولانی تر میکنه .

برای حل این مشکل ، تکنولوژی مجازی سازی ارائه شد که فقط به همون تعدادِ آیتم هایی که اون کنترلِ ItemsControl (مثل DataGrid) در حال نمایش اش هست و در صفحه نمایش ظاهر میشه (که معمولا تعداد اندک و فرضا 10 یا 20 آیتم را شما در یک لحظه در DataGrid میتونید ببینید) ، به همون تعداد ، کنترل بصری به ازای هر آیتمی که درون کنترلِ ItemsControl میبینید ، کنترلِ بصری در همون لحظه ساخته یا بازیافت میشه .

یعنی هر لحظه ای که اسکرولِ DataGrid را بالا یا پایین میکنید ، در همون لحظه ، اون چند تا کنترل ، ساخته یا بازیافت میشن (ولو اگه تعداد آیتم های DataGrid ، 1 میلیون عدد باشه) .

مجازی سازی ، هم برای سطر و هم برای ستون ، در DataGrid قابل فعال کردن هست که بصورت پیش فرض ، قابلیت مجازی سازی سطرها ، فعال هستند :

DataGrid.EnableRowVirtualization Property (System.Windows.Controls) | Microsoft Learn

احتمالا هم نوع مجازی سازی اش ، از نوع بازیافتی باشه .
حالت بازیافتی ، یعنی وقتی قبلا یه آیتم ای (فرضا سطر ای) را دیده بودید و ساخته بود ، با مراجعه ی مجدد و برگردوندنِ اسکرول به همون آیتم ، دوباره به ازای اون آیتم ، کنترلِ بصریِ جدیدی ساخته نمیشه و از همون شی ای که قبلا براش ساخته شده بود ، استفاده میکنه (که مشکل پست اول تون و همچنین مشکل پست شماره 9 ، بخاطر همین حالت از مجازی سازی هست) .

حالت دیگه ی مجازی سازی ، حالت استاندارد هست که این طور هست که هر وقت ، هر آیتمی در ItemsControl (مثل DataGrid) نمایش داده بشه (توسط بالا یا پایین کردن اسکرول و ...) ، به ازای اون آیتم ها ، کنترلِ بصریِ جدیدی ساخته میشه . چه قبلا دیده بودید و کنترلی وجود داشت یا نه :

VirtualizationMode Enum (System.Windows.Controls) | Microsoft Learn

==========

راهکار :

برای اینکه همون مشکلی که در پست 9 و 3 هست رفع بشه ، باید حالتِ مجازی سازی را در DataGrid ، روی حالتِ استاندارد (Standard) بذارید :

wpf - DataGrid row virtualization display issue - Stack Overflow

برای این کار ، طبق لینک بالا (و همچنین لینک قبلی تر) ، در DataGrid (یا هر ItemsControl ای که مورد نظرتون هست) ، دو پروپرتیِ زیر را قرار بدید :

XML:
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"

یعنی مثلا این طوری :

XML:
<DataGrid Name="PhoneBookDataGrid" HorizontalAlignment="Center" Height="240"
      Margin="0,153,0,0" VerticalAlignment="Top" Width="780"
      AutoGenerateColumns="False"
      ItemsSource="{Binding ValidatesOnExceptions=True}"


      AlternationCount="{Binding Count}"
      VirtualizingStackPanel.IsVirtualizing="True"
      VirtualizingStackPanel.VirtualizationMode="Standard">


    <DataGrid.Columns>


        <DataGridTextColumn Header="ردیف" IsReadOnly="True"
    Binding="{Binding AlternationIndex,
    RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>


    </DataGrid.Columns>


</DataGrid>


دقت کنید که اون دو خط کد را اگه در DataGrid با دست بخواین بنویسین ، توی لیست اینتلایسنس ، نمیاره .
فرضا اگه عبارتِ VirtualizingStackPanel را بنویسید ، و نقطه بذارید ، عضوِ IsVirtualizing اش را نمیاره .
بلکه باید خودتون این خط ها را در DataGrid (و ...) ، کپی و paste کنین .

راهکار دیگه اینه که کلا مجازی سازی سطرها را غیرفعال کنید که پیشنهاد نمیشه .
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
همچنان ای کاش دوباره سریعتر برگردین :cry: و عیدتون مبارک باشه .
ازتون خیلی درس ها یاد گرفتم :rose:

نحوه ی همزمان تغییر رنگ یکی درمیان DataGridRow و شماره بندی ردیف سطرها :
نیاز به تغییر AlternationCount نیست . فقط کافی هست که برای Binding به AlternationIndex ، یک Converter برای تغییر رنگ پشت زمینه ی DataGridRow تعریف کنیم :

XML:
<DataGrid ItemsSource=“{Binding YourItemsSource}” AlternationCount=“{Binding RowCount}”>
    <DataGrid.Resources>
        <local:AlternationIndexToColorConverter x:Key=“AlternationIndexToColorConverter”/>
    </DataGrid.Resources>
    
    <DataGrid.Columns>
        <DataGridTemplateColumn Header=“Row Number”>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text=“{Binding RelativeSource={RelativeSource AncestorType=DataGridRow}, Path=AlternationIndex}”/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn> <!-- Other columns -->
    </DataGrid.Columns>
    
    <DataGrid.RowStyle>
        <Style TargetType=“DataGridRow”>
            <Setter Property=“Background” Value=“{Binding RelativeSource={RelativeSource Self}, Path=AlternationIndex,
            Converter={StaticResource AlternationIndexToColorConverter}}”/>
        </Style>
    </DataGrid.RowStyle>
</DataGrid>

و Converter :

C#:
public class AlternationIndexToColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int index = (int)value;
        if (index % 2 == 0)
            return Brushes.White;
        else
            return Brushes.LightGray;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
برای یادآوری :

Command ها در WPF :

Command ها به شما این امکان را می‌دهند که منطق تجاری خود را در ViewModel قرار دهید و آن را از View جداسازی کنید. در نتیجه، شما معمولاً از Command ها برای فراخوانی متدهایی استفاده می‌کنید که در لایه ViewModel قرار دارند.

(کارها و متدهای مربوط به View مثل نمایش متن مثل فراخوانی MessageBox.Show ، میتواند بجای View ، درون لایه ی ViewModel قرار بگیرد . چون ViewModel می‌تواند شامل منطق تجاری باشد که مستقیماً به View مرتبط است) .

اما اگر شما بخواهید یک متد را که در لایه Model قرار دارد (مثل متد AddStudent) فراخوانی کنید، شما باید این کار را از طریق ViewModel انجام دهید. به طور مثال، شما می‌توانید یک Command به نام AddStudentCommand در ViewModel خود تعریف کنید که در بدنه ی این Command ، متد AddStudent (که درون لایه ی Model قرار دارد) را فراخوانی کنید و سپس این Command را به یک دکمه در View متصل کنید. در نتیجه، هنگامی که کاربر روی این دکمه کلیک می‌کند، عملکرد مربوط به Command AddStudentCommand اجرا می‌شود.

شما می‌توانید به جای استفاده از Command، یک متد را که درون ViewModel نوشته‌اید را در هندلر رویداد Button_MouseEnter که در View قرار دارد ، فراخوانی کنید. با این حال، این کار نقض اصول معماری MVVM است زیرا در این صورت شما به طور مستقیم از View به ViewModel دسترسی پیدا می‌کنید.

در معماری MVVM، لایه ی View و ViewModel باید از یکدیگر جداسازی شوند و تنها از طریق Data Binding و Command ها با یکدیگر ارتباط برقرار کنند. در نتیجه، استفاده از Command ها به شما این امکان را می‌دهد که منطق تجاری خود را در ViewModel قرار دهید و آن را از View جداسازی کنید.

بنابراین، در یک برنامه MVVM، توصیه می‌شود که برای فراخوانی متدهای خود در ViewModel، از Command ها استفاده کنید.

هنگامی که شما خاصیت Command یک دکمه را به یک Command متصل می‌کنید، WPF به طور خودکار این Command را هنگام کلیک کردن روی دکمه فراخوانی می‌کند.
اما اگر شما بخواهید که یک Command را در زمان دیگری فراخوانی کنید (به عنوان مثال، هنگام قرار گرفتن موس روی یک دکمه)، شما باید به صورت دستی این کار را انجام دهید. به طور مثال (مثال شاید دقیق نباشد) :

C#:
private void Button_MouseEnter(object sender, MouseEventArgs e)
{
    if (ApplicationCommands.Copy.CanExecute(null, this))
    {
        ApplicationCommands.Copy.Execute(null, this);
    }
}

و ادامه ی کدها که به تعریف Command ها مربوط است :

C#:
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void CopyCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }

    private void CopyCommand_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        MessageBox.Show("Copy command executed");
    }

    private void CutCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }

    private void CutCommand_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        MessageBox.Show("Cut command executed");
    }
}

و xaml :

XML:
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
  <Window.CommandBindings>
      <CommandBinding Command="ApplicationCommands.Copy" CanExecute="CopyCommand_CanExecute" Executed="CopyCommand_Executed"/>
      <CommandBinding Command="ApplicationCommands.Cut" CanExecute="CutCommand_CanExecute" Executed="CutCommand_Executed"/>
  </Window.CommandBindings>
  <Grid>
      <StackPanel>
          <Button Command="ApplicationCommands.Copy">Copy</Button>
          <Button Command="ApplicationCommands.Cut">Cut</Button>
      </StackPanel>
  </Grid>
</Window>
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
یادآوری :

اجرای Command در زمانی که رویداد خاصی اتفاق میافتد (بجز زمان اجرای رویداد پیش فرض کنترل) :

مثلا وقتی Mouse Enter یا هر رویداد غیر پیش فرض که خواست اجرا بشود ، بتوانیم Command ای را اجرا کنیم .
در این صورت :


1) یا بصورت روال عادی ، متدی را به آن رویداد مورد نظرمان (مثلا رویداد MouseEnter) متصل میکنیم و درون بدنه ی آن متد ، از Command مان استفاده و متد Execute اش را فراخوانی میکنیم :

کد تعریف Command :

C#:
public class MyCommand : ICommand
{
    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        // Logic to determine if the command can be executed
        return true;
    }

    public void Execute(object parameter)
    {
        // Logic to execute the command
    }
}

و در متد رویداد مورد نظرمان (مثل MouseEnter یا AddingNewItem و ...) :

C#:
private void MyDataGrid_AddingNewItem(object sender, AddingNewItemEventArgs e)
{
    var myCommand = new MyCommand();
    if (myCommand.CanExecute(null))
    {
        myCommand.Execute(null);
    }
}

============

2) روش دوم ،
استفاده از Attached Property ای که خودمان میسازیم ، هست .

برای استفاده از یک Command در یک رویداد خاص با استفاده از Attached Property ، باید یک Attached Property جدید ایجاد کنید و سپس آن را به کنترل مورد نظر خود متصل کنید. هنگامی که مقدار Attached Property تغییر کند، می‌توانید نمونه‌ای از دستور خود را ایجاد کرده و سپس متد Execute آن را فراخوانی کنید.

مثال زیر نشان می‌دهد که چگونه می‌توان یک Attached Property به نام MouseEnterCommand ایجاد کرد و آن را به یک Button متصل کرد:

همان کد تعریف Command :

C#:
public class MyCommand : ICommand
{
    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        // Logic to determine if the command can be executed
        return true;
    }

    public void Execute(object parameter)
    {
        // Logic to execute the command
    }
}

کد تعریف Attached Property :

C#:
public static class CommandBehavior
{
    public static readonly DependencyProperty MouseEnterCommandProperty =
        DependencyProperty.RegisterAttached(
            "MouseEnterCommand",
            typeof(ICommand),
            typeof(CommandBehavior),
            new PropertyMetadata(null, OnMouseEnterCommandChanged));

    public static ICommand GetMouseEnterCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(MouseEnterCommandProperty);
    }

    public static void SetMouseEnterCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(MouseEnterCommandProperty, value);
    }

    private static void OnMouseEnterCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is UIElement element)
        {
            element.MouseEnter -= Element_MouseEnter;
            if (e.NewValue != null)
            {
                element.MouseEnter += Element_MouseEnter;
            }
        }
    }

    private static void Element_MouseEnter(object sender, MouseEventArgs e)
    {
        if (sender is UIElement element)
        {
            var command = GetMouseEnterCommand(element);
            if (command != null && command.CanExecute(null))
            {
                command.Execute(null);
            }
        }
    }
}

و در xaml :

XML:
<Button Content="Hover Me" local:CommandBehavior.MouseEnterCommand="{Binding MyCommand}" />

در این مثال، یک Attached Property به نام MouseEnterCommand ایجاد شده است. هنگامی که مقدار این Attached Property تغییر کند، متد OnMouseEnterCommandChanged فراخوانی می‌شود. در این متد، رویداد MouseEnter یک UIElement به متد Element_MouseEnter متصل می‌شود. هنگامی که رویداد MouseEnter رخ دهد، متد Element_MouseEnter فراخوانی میشود .
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
یادآوری :

اجرای Command در زمانی که رویداد خاصی اتفاق میافتد (بجز زمان اجرای رویداد پیش فرض کنترل) :
.


ادامه ی پست قبلی (یادآوری برای آینده) :

روش دوم ، که در پست قبلی گفته شد و همچنین روش سوم که در این پست گفته میشود ، برای استفاده در معماری MVVM مناسب است چون مقداردهی نوع داده ای ICommand از طریق Data Binding انجام شد (روش اول ، MVVM را نقض میکند) . این مقداردهی ، در کدهای xaml شان انجام شد .
روش دوم ، بخاطر ساده تر بودن نسبت به روش سوم و همچنین پشتیبانی از MVVM ، بیشتر توصیه میشود .

در روش دوم ، باید از attached property استفاده کنیم تا پروپرتی مان را در هر کنترلی از نوع UiElement ای که خواستیم ، متصل کنیم وگرنه فقط می‌توانیم آن را برای یک نوع خاص از کنترل‌ها استفاده کنیم و نمی‌توانیم آن را به هر نوع کنترل UIElement متصل کنیم.

در همه ی مثال های قبلی و این پست ، کلاس MyCommand ، درون لایه ی ViewModel تعریف میشود اما تمام کدها و کلاس های دیگر (مثل کلاس های CommandBehavior و MouseEnterCommandBehavior) ، درون لایه ی View تعریف میشوند .

================

3) روش سوم
هم مثل روش دوم ، همچنان به همان دلیل ، سازگار با معماری MVVM هست ؛ استفاده از Behavior است .

Behavior یک الگوی طراحی در WPF است که به شما امکان می‌دهد تا عملکرد جدیدی را به یک کنترل اضافه کنید. برای استفاده از یک Behavior، باید یک کلاس جدید ایجاد کنید که از کلاس Behavior<T> ارث‌بری کند و سپس عملکرد مورد نظر خود را در آن پیاده‌سازی کنید.

مثال زیر نشان می‌دهد که چگونه می‌توان یک Behavior به نام MouseEnterCommandBehavior ایجاد کرد و آن را به یک Button متصل کرد:

کد تعریف Command :

C#:
public class MyCommand : ICommand
{
    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        // Logic to determine if the command can be executed
        return true;
    }

    public void Execute(object parameter)
    {
        // Logic to execute the command
    }
}

کد تعریف کلاس Behavior<T> :

C#:
public class MouseEnterCommandBehavior : Behavior<Button>
{
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register(
            "Command",
            typeof(ICommand),
            typeof(MouseEnterCommandBehavior),
            new PropertyMetadata(null));

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.MouseEnter += AssociatedObject_MouseEnter;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.MouseEnter -= AssociatedObject_MouseEnter;
    }

    private void AssociatedObject_MouseEnter(object sender, MouseEventArgs e)
    {
        if (Command != null && Command.CanExecute(null))
        {
            Command.Execute(null);
        }
    }
}

و در xaml :

XML:
<Button Content="Hover Me">
    <i:Interaction.Behaviors>
        <local:MouseEnterCommandBehavior Command="{Binding MyCommand}" />
    </i:Interaction.Behaviors>
</Button>

در این مثال، یک Behavior به نام MouseEnterCommandBehavior ایجاد شده است. این Behavior دارای یک پروپرتی به نام Command است که به شما امکان می‌دهد تا یک دستور را به آن متصل کنید. هنگامی که Behavior به یک Button متصل شود، رویداد MouseEnter آن Button به متد AssociatedObject_MouseEnter متصل می‌شود. هنگامی که رویداد MouseEnter رخ دهد، متد AssociatedObject_MouseEnter فراخوانی میشود .
 
آخرین ویرایش:

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
سلام دوستان .

مطالبی که در زیر میگم ، هر چند تحقیق کردم اما 100 درصد مطمئن نیستم که درست باشه . دوستان لطفا راهنمایی کنن که این مطالب ، درست هست یا کجاها نادرست هست؟ :

یک جریان ، قضیه ی معماری MVVM هست و قضیه ی دیگه ، بالا بردن ماژولاریتی (Modular) و همچنین رعایت اصول Solid در طراحی نرم افزار و لایه ها .

در معماری MVVM ، فقط کافی هست که 3 لایه ی View و ViewModel و Model داشته باشیم اما ارتباط بین لایه های View و ViewModel ، چه توسط Command ها برقرار بشه ، یا نشه و بجاش توسط فراخوانیِ معمولیِ متدها انجام بشه ، معماری MVVM را نقض نمیکنه .
در واقع در MVVM ، مهم اینه که اون 3 لایه وجود داشته باشند و هر کدوم وظایف خودشون را انجام بدن .

به همین دلیل ، در MVVM ، وقتی حتی از Command ها هم استفاده نکنیم ، یا حتی اگه دسترسی مستقیم به اعضا و پروپرتی هایی که درون کلاس های ViewModel مون تعریف کردیم (از درون کلاس های View مون) داشته باشیم ، باعث نقض معماری MVVM نمیشه .

================

اما قضیه ی دوم ، اینه که قابلیت ماژولار بودن برنامه مون را بالا ببریم و همچنین اصول Solid (مخصوصا اصل Dependency Inversion Principle) را رعایت کنیم تا قابلیت نگه داری و تست کدمون را افزایش بدیم و مخصوصا اینکه اساسا معماری MVVM ، برای همین منظور طراحی شد (یعنی برای این طراحی شد که قابلیت نگه داری و تست کدمون را افزایش بده) ، به همین جهت ، خیلی در این معماری توصیه میشه که برای ارتباط بین لایه های View و ViewModel ، از Binding و Command استفاده کنیم . چرا؟

چون Command ها ، رابط و اینترفیسِ ICommand دارند و ارتباط باهاشون وقتی با رابط و اینترفیس انجام بشه ، اصل Dependency Inversion Principle در Solid را رعایت کرده و ماژولار بودن و قابلیت نگه داری و تست کد را به این ترتیب ، افزایش داده .
درست میگم؟

حالا میگم برای افزایش این بهره وری و افزایش قابلیت نگه داری کد ، مثل اغلب لایه های دیگه که با اینترفیس با هم در ارتباط هستند ، برای لایه ی ViewModel مون هم یه اینترفیس درست کنیم که اعضایی که نیاز به ارتباط برقرار کردن باهاشون داریم مثل پروپرتی ای از نوع ICommand و احیانا رویدادی که اگه نیاز شد ، پیغام ها (شامل موفقیت آمیز بودن یا ارورها) را به لایه ی View برای نمایش دادن ، منتقل کنه ، و لایه ی View ، فقط با این اینترفیس از ViewModel در ارتباط باشه (مستقیما با کلاس ها و اعضاش ارتباط نداشته باشه) (و این اینترفیس ، از این دست اعضا داشته باشه تا کلاسی که این را پیاده سازی میکنن ، هر کلاسی بتونن باشن و هر عضوی بتونن داشته باشن) . تا باعث بشه اصل DIP در Solid بیشتر رعایت بشه .

مثل کد زیر :

کد:
// ViewModel Layer


public interface IStudentViewModel
{
    event EventHandler NotifyToViewEvent;
    
    ICommand GetStudentCommand{get;set;}
}




public class StudentViewModel : IStudentViewModel
{
    public event EventHandler NotifyToViewEvent;
    
    public ICommand GetStudentCommand{get;set;}
    
    public CustomViewModel()
    {
        this.GetStudentCommand = new DelegateCommand(this.GetStudentExecute);
    }
    
    private void GetStudentExecute()
    {
        this.NotifyToViewEvent?.Invoke(this, new EventArgs());
    }
}


// View Layer


public class MyWindow : Window
{
    private IStudentViewModel myStudentViewModel;
    
    public MyWindow()
    {
        InitializeComponent();
        
        this.myStudentViewModel = new StudentViewModel();
        this.myStudentViewModel.NotifyToViewEvent += this.NotifyViewModelHandler;
        
        this.DataContext = this.myStudentViewModel;
    }
    
    private void NotifyViewModelHandler(object sender, EventArgs e)
    {
        MessageBox.Show(e.ToString());
    }
}

و در xaml :

کد:
<Button Content="Get Student" Command="{Binding Path = GetStudentCommand}"/>

اما همچین ساختاری را خیلی جای کمی دیدم و نمیدونم مرسوم هست یا کلا روال غیر عادی که نیست دیگه؟ به نظر خودم درسته .
برای بهتر کردن کد هم میشه از الگوی طراحی Factory Method یا Service Locator استفاده کرد .

رویداد NotifyToViewEvent هم در کد بالا ، تقریبا فقط میشه گفت همین یک رویداد کافی هست تا همه ی متدهایی که Command ها را اجرا میکنن ، این رویداد را فراخوانی کنن . یعنی همین رویداد ، به ازای همه ی Command کافی هست چون فقط وظیفه ی انتقال هر نوع پیغام از هر Command ای را داره .

=======

بنابراین حتی با رعایت اصول Solid در MVVM هم اشکالی نداره که در کد سی شارپ ، لایه ی View و ViewModel با هم ارتباط داشته باشند :


Simplifying the WPF TreeView by Using the ViewModel Pattern - CodeProject

البته در لینک بالا ، چون شیِ _familyTree اش در کلاس TextSearchDemoControl ، از نوع کلاس هست (و از نوع اینترفیس یا کلاس abstract نیست) ، اصل DIP در Solid را نقض میکنه .

======

بنابراین هیچ مشکلی نداره که یک Command مون را از توی یک رویداد عادی اجرا کنیم . یعنی بجای Command ، یک رویداد عادی برای کنترل مون درست کنیم و Command ای که در لایه ی ViewModel درست کردیم را از توی View ، فراخوانی کنیم .

این ، به درد جایی میخوره که فرضا بخشی از کدمون در لایه ی View و بخشی هم در لایه ی ViewModel وجود داشته باشه .
یعنی مثلا بخوایم یه Dialog ای را برای انتخاب فایل باز کنیم (که این تیکه مربوط به وظایف لایه ی View هست) و بعد از اون ، یک متدی را از Model فراخوانی کنیم که باید برای این کار ، Command مون را در ViewModel اجرا کنیم .

بنابراین میتونیم یه هندلر Button_Click ای در View درست کنیم (و بعد از انجام کارهای مربوطه) ، در همون هندلر ، Command مربوطه در ViewModel (فرضا GetStudentCommand در کد بالا) را فراخوانی کنیم .
هر چند احتمالا این سناریو ، کم پیش میاد و بیشتر سناریوها ، مستقیما در کد xaml ، اون Command ای را که در ViewModel مون ساخته بودیم را مستقیما فراخوانی اش میکنیم .

===========

یک سناریوی دیگه هم اینه که وقتی که یک رویداد خاصی از یک المنت و کنترل مورد نظرمون ، اجرا شد ، در این صورت ، Command مون اجرا بشه که کتابخونه های زیادی برای این کار هست (از جمله کتابخونه ی پیش فرض Silverlight و MVVMLight و DevExpress و ...) اما کتابخانه ی Microsoft.Xaml.Behaviors.Wpf هم هست که خوبه و توسط nuget میشه ازش استفاده کرد :

کد:
<Window x:Class="MVVM_Practies.MainWindow"
    xmlns:behavior ="http://schemas.microsoft.com/xaml/behaviors">
    
    <Button Content="Mouse Move">
        <behavior:Interaction.Triggers>
            <behavior:EventTrigger EventName="MouseMove">
                <behavior:InvokeCommandAction  Command="{Binding Path=GetStudentCommand}"
                    PassEventArgsToCommand="True"/>
            </behavior:EventTrigger>
        </behavior:Interaction.Triggers>
    </Button>


</Window>

که برای بیشترین سناریوها ، سناریو کد xaml بالا که برای رویداد خاص و کنترل خاصی استفاده میشه و همچنین مستقیما Command را در ViewModel فراخوانی میکنه .

پاورقی اینکه RelayCommand هم کلاس سبک شده ی DelegateCommand هست (که اغلب از این نوع ها استفاده میشه).

کلاس RoutedCommand هم مثل RoutedEvent ها ، اما این کلاسِ RoutedCommand ، اگه هر وقت اجرا بشه ، فقط استراتژی حبابی و به سمت بالا را طی میکنه تا به کنترلی که شی ای از نوع CommandBinding (که معمولا پروپرتیِ CommandBindings در المنت ها هستند) ای که شیِ Command هاشون یکی و هم خوان اش باشه (مثلا هر دو از نوع ApplicationCommands.Copy باشن ، برسه که در اون صورت ، مقداری که درون پروپرتیِ Command ای که در شیِ CommandBinding مشخص شده را اجرا میکنه .

و RelayCommand ، بیشتر به درد زمانی میخوره که در دو کنترل مختلف (مثلا هم در MenuItem و هم در کنترل دکمه) ، بخوایم ییک کار مشابه (مثل کپی کردن و ...) را انجام بدیم .

درست هست دیگه؟

تشکر .
 
آخرین ویرایش:

golex

Member
من نمیدونستم WPF چی هست اومدم پستتون رو بخونم و سر در بیارم دیدم اوه خیلی تخصصی شد
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
وظیفه ی لایه ی ViewMode در MVVM ، علاوه بر پل ارتباطی بین لایه ی View و Model ، وظیفه ی دیگه ای هم داره که اون هم کلاس ها و کلا ماژول های مربوط به "منطق نمایش" (یا Presentation Logic) را در این لایه (ی ViewMode) تعریف میکنند .
یعنی کارهایی که منطق نمایش کنترل ها را انجام میده . مثلا از ماژول مدیریت آیتم ها و فیلترها و گروه بندی CollectionView گرفته تا ماژول اعتبارسنجی و ... .

منطق نمایش ، با منطق تجاری (که در لایه ی Model تعریف میشه) ، فرق داره .
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
سلام
درباره ی قضیه ی Self و TemplatedParent مثلا در کد زیر :

XML:
<ControlTemplate x:Key="RibbonTabHeaderControlTemplateKey" TargetType="{x:Type ribbon:RibbonTabHeader}">
    <Border x:Name="PART_OuterBorder" SnapsToDevicePixels="True"
        Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
        BorderThickness="{TemplateBinding BorderThickness}" Margin="{TemplateBinding Padding}">
        <ContentPresenter x:Name="ContentPresenter" VerticalAlignment="Center" HorizontalAlignment="Center" />
    </Border>

    <ControlTemplate.Triggers>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsEnabled" Value="False" />
                <Condition Property="IsRibbonTabSelected" Value="True"/>
            </MultiTrigger.Conditions>
            <Setter TargetName="ContentPresenter" Property="TextBlock.Foreground"
                Value="{DynamicResource RibbonTabHeader_DisabledForegroundBrush}"/>
                
            <Setter TargetName="PART_OuterBorder" Property="BorderBrush"
                    Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=FocusedBorderBrush}"/>
                    
        </MultiTrigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

در خط زیر در کد بالا :

XML:
<Setter TargetName="PART_OuterBorder" Property="BorderBrush"
    Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=FocusedBorderBrush}"/>

Binding Target Property مان چیست؟
پروپرتی BorderBrush هست .

Binding Target Object مان چیست؟
شی TargetName که همان نام PART_OuterBorder را دارد (از نوع Border) هست . چون در اینجا TargetName را مشخص کردیم ، شی هدف Binding مان به همین پروپرتی ای که مشخص کردیم ، تغییر میکند .

خوب ، پس در اینجا ، Binding Target Object مان ، همان کنترل Border ای با نام PART_OuterBorder هست . وقتی Self را به عنوان Mode ئه RelativeSource مشخص میکنیم ، به همان Binding Target Object مان Binding میشود و چون پروپرتی FocusedBorderBrush در کنترل Border وجود ندارد ، عمل Binding نا موفق میشود .

اما اگر TargetName را مقداردهی نکنیم ، Binding Target Object مان همان نوعی که ControlTemplate بهش اشاره دارد ، یعنی از نوع RibbonTabHeader در نظر گرفته میشود و بنابراین Self هم به همین شی اشاره دارد که در این صورت ، برابر با مقدار TemplatedParent در Mode ئه RelativeSource میشود . یعنی اگر TargetName را مقداردهی نکنیم ، مقدار TemplatedParent و مقدار Self در Mode ئه RelativeSource ، با هم یکی میشوند .
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
سلام
درباره ی قضیه ی Self و TemplatedParent مثلا در کد زیر :

XML:
<ControlTemplate x:Key="RibbonTabHeaderControlTemplateKey" TargetType="{x:Type ribbon:RibbonTabHeader}">
    <Border x:Name="PART_OuterBorder" SnapsToDevicePixels="True"
        Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
        BorderThickness="{TemplateBinding BorderThickness}" Margin="{TemplateBinding Padding}">
        <ContentPresenter x:Name="ContentPresenter" VerticalAlignment="Center" HorizontalAlignment="Center" />
    </Border>

    <ControlTemplate.Triggers>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsEnabled" Value="False" />
                <Condition Property="IsRibbonTabSelected" Value="True"/>
            </MultiTrigger.Conditions>
            <Setter TargetName="ContentPresenter" Property="TextBlock.Foreground"
                Value="{DynamicResource RibbonTabHeader_DisabledForegroundBrush}"/>
              
            <Setter TargetName="PART_OuterBorder" Property="BorderBrush"
                    Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=FocusedBorderBrush}"/>
                  
        </MultiTrigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

در خط زیر در کد بالا :

XML:
<Setter TargetName="PART_OuterBorder" Property="BorderBrush"
    Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=FocusedBorderBrush}"/>

Binding Target Property مان چیست؟
پروپرتی BorderBrush هست .

Binding Target Object مان چیست؟
شی TargetName که همان نام PART_OuterBorder را دارد (از نوع Border) هست . چون در اینجا TargetName را مشخص کردیم ، شی هدف Binding مان به همین پروپرتی ای که مشخص کردیم ، تغییر میکند .

خوب ، پس در اینجا ، Binding Target Object مان ، همان کنترل Border ای با نام PART_OuterBorder هست . وقتی Self را به عنوان Mode ئه RelativeSource مشخص میکنیم ، به همان Binding Target Object مان Binding میشود و چون پروپرتی FocusedBorderBrush در کنترل Border وجود ندارد ، عمل Binding نا موفق میشود .

اما اگر TargetName را مقداردهی نکنیم ، Binding Target Object مان همان نوعی که ControlTemplate بهش اشاره دارد ، یعنی از نوع RibbonTabHeader در نظر گرفته میشود و بنابراین Self هم به همین شی اشاره دارد که در این صورت ، برابر با مقدار TemplatedParent در Mode ئه RelativeSource میشود . یعنی اگر TargetName را مقداردهی نکنیم ، مقدار TemplatedParent و مقدار Self در Mode ئه RelativeSource ، با هم یکی میشوند .

سلام
همچنین اینکه در شی Condition یا Trigger ، در پروپرتی Property شون ، نمیتونیم چندین پروپرتی تو در تو را بدیم . مثلا نمیتونیم بنویسیم :

XML:
<Condition Property="Ribbon.IsDropDownOpen"/>

چون دو پروپرتی تو در تو را مشخص کردیم .
برای اینکار ، باید از Binding (و همچنین پروپرتی Binding) در شی Condition استفاده کنیم .

همچنین در پروپرتی Binding ئه Condition هم اگر نیاز به تعریف منبع RelativeSource به شی کنترل و المنت ای که داریم برایش تمپلیت مینویسیم را داریم ، Mode ئه RelativeSource اش را باید روی Self بذاریم و حالت TemplatedParent جواب نمیده .
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
درباره ی رویدادها در wpf :


وقتی استراتژی حباب باشد ، ابتدا رویداد همان شی منبع (source) اجرا میشود . بعد در درخت بصری به سمت بالا میرود و اگر هر المنت ای که هندلری به همان رویداد به آن متصل شده را هم اجرا میکند .

مثلا در همین مثال Window>Grid>Button ، اگر وقتی فقط برای رویداد Button.MouseDown هندلری متصل کرده باشیم ، با کلیک روی دکمه ، ابتدا همین هندلر اجرا میشود (در واقع همان رویداد UIElement.MouseDown را به هندلری متصل کردیم) .

و سپس سمت بالا که کنترل Grid هست میرود و چون Grid هم از نوع UIElement هست ، پس بررسی میکند که آیا هندلری به رویداد UIElement.MouseDown برای شی Grid متصل شده یا نه ؟ اگر هندلری به رویداد Grid.MouseDown (یا همان هندلری به رویداد UIElement.MouseDown ئه شی Grid) متصل شده بود ، آن را اجرا میکند وگرنه اجرا نمیکند (یعنی اگر قبلا هندلری به رویداد Grid.MouseDown متصل نکرده باشیم ، این طور نیست که همان هندلرِ Button.MouseDown را دوباره اجرا کند) .

و سپس مجددا به سمت بالا که Window هست میرود و بررسی میکند که آیا هندلری به رویداد UIElement.MouseDown برای شی Window متصل شده یا نه ؟ اگر هندلری به رویداد Window.MouseDown (یا همان هندلری به رویداد UIElement.MouseDown ئه شی Window) متصل شده بود ، آن را اجرا میکند وگرنه اجرا نمیکند (یعنی اگر قبلا هندلری به رویداد Window.MouseDown متصل نکرده باشیم ، این طور نیست که همان هندلرِ Button.MouseDown را دوباره اجرا کند) .

یعنی در استراتژی حباب ، اگر میخواهیم فقط یک هندلر برای یک شی مان اجرا شود ، حتما باید حواسمان به المنت ها در قسمتِ بالاتر از آن المنت مورد نظر در درخت بصری باشد (مثلا حواسمان به Grid و Window در این مثال باشد) (و در Button هم e.Handled را true کنیم) .



استراتژی تونل هم که معکوسِ استراتژی حباب هست .
یعنی فقط این بار از ریشه که Window هست شروع میکند و منتها فقط رویداد خودش که با Preview شروع میشود را بررسی میکند . یعنی مثلا رویداد PreviewMouseDown را بررسی میکند و دیگر به رویداد MouseDown کاری ندارد .

و همچنین در استراتژی تونل ، حواسمان به المنت ها در قسمتِ پایین تر از آن المنت مورد نظر در درخت بصری باشد .
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
در DockPanel :

XML:
<DockPanel>
    <Button DockPanel.Dock="Top">من در بالا هستم</Button>
    <Button DockPanel.Dock="Bottom">من در پایین هستم</Button>
    <Button DockPanel.Dock="Left">من در چپ هستم</Button>
    <Button DockPanel.Dock="Right">من در راست هستم</Button>
    <ListBox>من در مرکز هستم</ListBox>
</DockPanel>

و

XML:
<DockPanel>
    <ListBox DockPanel.Dock="">من در مرکز هستم</ListBox>
    <Button DockPanel.Dock="Top">من در بالا هستم</Button>
    <Button DockPanel.Dock="Bottom">من در پایین هستم</Button>
    <Button DockPanel.Dock="Right">من در راست هستم</Button>
    <Button DockPanel.Dock="Left">من در چپ هستم</Button>
</DockPanel>

همیشه فرزندی که در DockPanel میخواهیم اضافه کنیم که در مرکز اش قرار گیرد ، همیشه باید آخرین کنترلی باشد که اضافه میکنیم .

بقیه ی کنترل هایی که در سمت بالا و پایین و چپ و راست قرار میگیرند ، چندان ترتیبات شان مهم نیست .
البته مهم هست . چون فرضا اگر کنترل اول که در بالا اضافه میکنیم و کل فضای بالا را (از لحاظ عرض) پُر کند ، حالا کنترل دوم را که به سمت چپ اضافه کنیم ، در سمت چپ ، اما از ادامه ی جایی که کنترل اول قرار دارد ، قرار میگیرد . یعنی از بالا ، به اندازه ی ارتفاع کنترلِ اول ، قرار نمیگیرد بلکه بعد از ارتفاعِ کنترل اول قرار میگیرد . اما اگر ترتیب شان برعکس بود ، ابتدا کلِ سمت چپ ، از بالا تا پایین را کنترلی که در سمت چپ قرار داشت ، پُر میکرد .
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
در معماری mvvm ، تعریف Converter ها برای Binding ، اغلب ، وظیفه ی View هست .
چون در این معماری ، لایه ی View و ViewModel تا میتونند باید جدا از هم باشند و جدا از هم کار کنند . یعنی لایه ی View باید بتونه به تنهایی کار کنه .

اما چون Binding را در لایه ی View انجام میدیم ، پس Converter ها برای Binding ، جزء وابستگی های Binding میشه . یعنی Binding ، بدون Converter ، کار ناقص میشه .
ضمنا اغلبِ وظیفه ی Converter ها این هست که معمولا داده را به فرمت و نوعی که قابل نمایش در ui باشه ، تبدیل کنند .

بنابراین اغلب ، تعریفِ Converter ها در لایه ی View انجام میشه .
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
در معماری mvvm ، تعریف Converter ها برای Binding ، اغلب ، وظیفه ی View هست .
چون در این معماری ، لایه ی View و ViewModel تا میتونند باید جدا از هم باشند و جدا از هم کار کنند . یعنی لایه ی View باید بتونه به تنهایی کار کنه .

اما چون Binding را در لایه ی View انجام میدیم ، پس Converter ها برای Binding ، جزء وابستگی های Binding میشه . یعنی Binding ، بدون Converter ، کار ناقص میشه .
ضمنا اغلبِ وظیفه ی Converter ها این هست که معمولا داده را به فرمت و نوعی که قابل نمایش در ui باشه ، تبدیل کنند .

بنابراین اغلب ، تعریفِ Converter ها در لایه ی View انجام میشه .

در DockPanel :

XML:
<DockPanel LastChildFill="False">

    <Image Name="Image" DockPanel.Dock="Top"/>

    <TextBox Name="UserNameTextBox" DockPanel.Dock="Top"/>

    <TextBox Name="PasswordTextBox" DockPanel.Dock="Top" />

    <Label Name="SignUpLabel" DockPanel.Dock="Bottom" />

    <Button Name="LogInButton" DockPanel.Dock="Bottom" />

</DockPanel>

وقتی شما مقدار DockPanel.Dock را برای یک کنترل به Bottom تنظیم می‌کنید، این کنترل در پایین DockPanel قرار می‌گیرد، اما همچنان فضای باقی‌مانده را برای کنترل‌های بعدی اختصاص می‌دهد. به همین دلیل، کنترل‌هایی که DockPanel.Dock آن‌ها Bottom است، به ترتیب معکوس چیدمان می‌شوند. یعنی اولین کنترل با DockPanel.Dock="Bottom" در پایین‌ترین قسمت قرار می‌گیرد و کنترل‌های بعدی در بالاتر از آن قرار می‌گیرند.

اول از همه که کنترلِ SignUpLabel را در Bottom قرار دادم ، پس این کنترل ، زودتر از بقیه در پایین قرار میگیرد (اولین کنترل از پایین محسوب میشود) و بعدش که LogInButton را در کد بعدی نوشتم ، چون دومین کد از پایین محسوب میشود ، پس این کنترل ، دومین کنترلی هست که از پایین اضافه میشود (مثل چیدمان از بالا) .

پس به ترتیبِ کدهایی که ما اضافه میکنیم ، اضافه نمیشود .
بلکه به ترتیبِ چیدمان از بالا و پایین و چپ و راست اضافه میشود .

یعنی از بالا ، ابتدا کنترل Image و پایین تر از آن کنترل UserNameTextBox و پایین ترش هم کنترل PasswordTextBox قرار دارد که درست است . اما کنترل هایی که DockPanel.Dock شان مقدار Bottom دارد ، ترتیب شان برعکس میشود . یعنی ابتدا کنترل LogInButton قرار دارد (این کنترل ، در زیرِ کنترلِ PasswordTextBox قرار دارد) و در پایین تر از آن ، کنترل SignUpLabel قررر دارد ! در صورتی که من در کد بالا ، ابتدا کنترل SignUpLabel را نوشتم .
 

جدیدترین ارسال ها

بالا