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

SajjadKhati

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

پس ، در کل منظور شما اینه که ، استراکچر (یا کلاسِ) تنظیمات ، یا باید در View و یا در Model و حتی درون یک برنامه ، میتونه دو تا استراکچرِ تنظیمات باشه که یکی اش در View و یکی دیگه در Model باشه ؟
درست متوجه شدم؟

الان در عکس پست 932 که از برنامه گذاشتم ، عکسِ (سربرگِ) اول و سوم ، به Model ئه برنامه ام مربوط میشه و روی اونها تاثیر میذاره دیگه . درست میگم؟
هر چند Binding از View به Model ام انجام میشه تا مشخص بشه تنظیمات ، چطور تنظیم شدن (یعنی فرضا گزینه ی اول که "اجرای برنامه زمان شروع ویندوز" هست ، آیا تیک داره یا نه و ...) اما تنظیمات این دو سربرگ ، فقط جنبه ی نمایشی در View دارن اما تاثیرشون توی Model هست .
یعنی شما میگید برای این دو سربرگ ، استراکچری برای این تنظیمات (مربوط به این دو سربرگ) را در Model ایجاد کنم؟


و همچنین سربرگ (عکس) دوم ، توی ظاهر یا View ئه برنامه ام تاثیر داره (یعنی انتخاب استایل ، روی View ئه برنامه ام تاثیر داره) .
منظور شما اینه که یه استراکچرِ دیگه از تنظیمات ، درون لایه ی View برای سربرگ دوم ام بسازم؟

اگه آره ، این جوری ، کار یه کم پیچیده نمیشه؟

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

بعد اینکه استاد ، اول ، اصلا به نظرتون من معادل هر فیلد در استراکچرِ تنظیمات ، پروپرتیِ متناظرش را در کلاس مورد نظرم داشته باشم یا نه؟
فرضا (در اون فایل Visio) که پروپرتیِ ExceptionDrives در کلاسِ InternalHardDriveCollection بود ، من بیام در این کلاس ، پروپرتیِ ExceptionDrives تعریف کنم یا اینکه لازم نیست و مستقیما مقدارش را از استراکچرِ تنظیماتم در مواقع مورد نیاز فراخونی کنم؟
با توجه به اینکه اولا پروپرتی نیاز دارم تا این پروپرتی ام در Model را به View ام Binding کنم و دوما این پروپرتی ها (مثل پروپرتیِ ExceptionDrives در کلاسِ InternalHardDriveCollection) ، جزء مشخصه و ویژگیِ خودِ همون کلاس هست ، نظر خودم اینه که به همین منوال پیش برم . یعنی متناظرِ فیلدهای استراکچر ، پروپرتیِ مورد نظر را در کلاس مورد نظرم ایجاد کنم (نظرم ، همون روالی که در فایل Visio نوشتم ، هست) .
درست میگم؟


یه مورد دیگه این بود که فرضا بجای اینکه بیام در متد مورد نظر در هر کلاس (متد با نام UpdateSettingRelatedProperties در اون فایل Visio) ، بعد از به روزرسانیِ مقادیر پروپرتی مورد نظر در اون کلاس ، بعدش بیام این مقادیر را در این متد ، در دیتابیس ذخیره کنم ، بجاش یه متدی در خودِ استراکچرِ تنظیمات بسازم و از اونجا در دیتابیس ذخیره کنم .
نظرتون در این رابطه چیه؟ در استراکچر تنظیمات ، متدی بسازم یا در همون کلاس (با متد UpdateSettingRelatedProperties) برای ذخیره سازی در دیتابیس ؟
نظر من اینه که در همون کلاس این کار را انجام بدم (همون روالی که در اون فایل Visio نوشتم) .

سلامی مجدد .
بخش اول از سئوال را اندکی ویرایش میکنم :

ویرایش :
الان در عکس پست 932 که از برنامه گذاشتم ، عکسِ (سربرگِ) اول و سوم ، اغلب گزینه های این دو عکس ، به Model ئه برنامه ام مربوط میشه و روی اونها تاثیر میذاره دیگه . درست میگم؟
هر چند Binding از View به Model ام انجام میشه تا مشخص بشه تنظیمات ، چطور تنظیم شدن (یعنی فرضا گزینه ی زیرِ کمبوباکس که بکاپ گیری اتوماتیک هست و استثناعات درایو و ...) اما تنظیمات این دو سربرگ ، فقط جنبه ی نمایشی در View دارن اما تاثیرشون توی Model هست .
یعنی شما میگید برای این دو سربرگ ، استراکچری برای این تنظیمات (مربوط به این دو سربرگ) را در Model ایجاد کنم؟


و همچنین سربرگ (عکس) دوم ، توی ظاهر یا View ئه برنامه ام تاثیر داره (یعنی انتخاب استایل ، روی View ئه برنامه ام تاثیر داره) . همچنین دو گزینه ی اول از عکس اول در View ام تاثیر داره (گزینه ی اجرای برنامه زمان شروع ویندوز) .
منظور شما اینه که یه استراکچرِ دیگه از تنظیمات ، درون لایه ی View برای سربرگ دوم ام بسازم؟

اگه آره ، این جوری ، کار یه کم پیچیده نمیشه؟
 

the_king

مدیرکل انجمن
سلامی مجدد
خیلی ممنون استاد .

پس ، در کل منظور شما اینه که ، استراکچر (یا کلاسِ) تنظیمات ، یا باید در View و یا در Model و حتی درون یک برنامه ، میتونه دو تا استراکچرِ تنظیمات باشه که یکی اش در View و یکی دیگه در Model باشه ؟
درست متوجه شدم؟
بله. بدیهی ئه. تنظیمات صرفا داده است، هیچ فرقی با داده های دیگه نداره. موضوع اصلا تنظیمات نیست، تنظیمات که در شیء گرایی یا MVVM یک ماهیت ویژه و منحصر بفرد نداره. ساختار x ای که لایه View لازم داره باید کجا باشه؟ در View. ساختار y ای که لایه Model لازم داره باید کجا باشه؟ در Model. غیر از این باشه که لایه محدوده متمایزی نداره، وابسته شده به چیزی که داخل محدوده تعیین شده نیست. اصلا مهم نیست که x نقش تنظیمات رو داره یا نه. فرقی نمی کنه که این ساختار کارکردش تنظیمات ئه یا چیز دیگری.
الان در عکس پست 932 که از برنامه گذاشتم ، عکسِ (سربرگِ) اول و سوم ، به Model ئه برنامه ام مربوط میشه و روی اونها تاثیر میذاره دیگه . درست میگم؟
اگر در کلاسی در ViewModel دارید به Model مقادیر جدید تنظیمات رو ارسال می کنید (که به واسطه فراخوانی متدی از طرف View ئه) که روال عادیه ViewModel ئه و ایرادی هم نداره. اینکه در Model این ارسال مقادیر تاثیری میذاره یا نمیذاره، به Model و کدش بستگی داره و به ViewModel و View هم ربطی نداره، روال داخلی Model ئه که تصمیم میگیره این مقادیر باید در کارکردش تاثیر بذارند یا نه.
هر چند Binding از View به Model ام انجام میشه تا مشخص بشه تنظیمات ، چطور تنظیم شدن (یعنی فرضا گزینه ی اول که "اجرای برنامه زمان شروع ویندوز" هست ، آیا تیک داره یا نه و ...) اما تنظیمات این دو سربرگ ، فقط جنبه ی نمایشی در View دارن اما تاثیرشون توی Model هست .
یعنی شما میگید برای این دو سربرگ ، استراکچری برای این تنظیمات (مربوط به این دو سربرگ) را در Model ایجاد کنم؟
تنظیم اجرای برنامه زمان شروع ویندوز تاثیری در کارکرد Model نداره. Model فقط مقدار رو ذخیره یا واکشی می کنه. این View ئه که وقتی این مقدار true شد، کارکرد متفاوتی داره، مثلا بجای اینکه پنجره اصلی برنامه رو باز کنه، صرفا به آیکون در System Tray بسنده می کنه. Model میتونه استراکچر داشته باشه، اما واقعا نیازی به استراکچر خاصی داره؟ تنظیمات یکسری name هستند با یکسری value که تعدادشون هم میتونه به مرور کم و زیاد بشه.
و همچنین سربرگ (عکس) دوم ، توی ظاهر یا View ئه برنامه ام تاثیر داره (یعنی انتخاب استایل ، روی View ئه برنامه ام تاثیر داره) .
منظور شما اینه که یه استراکچرِ دیگه از تنظیمات ، درون لایه ی View برای سربرگ دوم ام بسازم؟
می توانید برای اینترفیس View استراکچر بسازید یا نسازید. دست خودتونه. اما طبعا چون مقادیر اون تنظیمات در کارکرد View تاثیر داره، نیاز به مشخصه یا فیلد با اسامی تعیین شده داره، مثل Model نیست که برایش مهم نباشه این تنظیم name برای چیه.
اگه آره ، این جوری ، کار یه کم پیچیده نمیشه؟
مثل اینه که بخواهید شنا کنید اما خیس نشوید. کد پروژه های مختلف رو در github.com ببینید و بررسی کنید بین شون موردی هست که به نظرتون
پیچیده نباشه؟ پیچیدگی جزئی از ذات قضیه است. اگر هیچ کدی ننویسید پیچیده نمیشه، این تنها راه ئه.
بعد اینکه استاد ، اول ، اصلا به نظرتون من معادل هر فیلد در استراکچرِ تنظیمات ، پروپرتیِ متناظرش را در کلاس مورد نظرم داشته باشم یا نه؟
نظر من ملاک نیست، طراح اینترفیس شما هستید. اگر واسطه ارتباط View و ViewModel مشخصه ها برای هر تنظیم و Binding باشه که دیگه ساختار نمیخواد. اگر هم واسطه ارتباط شون ساختار باشه که دیگه مشخصه های جزئی رو لازم نداره.
فرضا (در اون فایل Visio) که پروپرتیِ ExceptionDrives در کلاسِ InternalHardDriveCollection بود ، من بیام در این کلاس ، پروپرتیِ ExceptionDrives تعریف کنم یا اینکه لازم نیست و مستقیما مقدارش را از استراکچرِ تنظیماتم در مواقع مورد نیاز فراخونی کنم؟
بالاخره یکی از این دو رو انتخاب می کنید، هر دوشون که لازم نیست.
با توجه به اینکه اولا پروپرتی نیاز دارم تا این پروپرتی ام در Model را به View ام Binding کنم و دوما این پروپرتی ها (مثل پروپرتیِ ExceptionDrives در کلاسِ InternalHardDriveCollection) ، جزء مشخصه و ویژگیِ خودِ همون کلاس هست ، نظر خودم اینه که به همین منوال پیش برم . یعنی متناظرِ فیلدهای استراکچر ، پروپرتیِ مورد نظر را در کلاس مورد نظرم ایجاد کنم (نظرم ، همون روالی که در فایل Visio نوشتم ، هست) .
درست میگم؟
Binding کردن به این معنی نیست که مجبور هستید برای هر تنظیم یک مشخصه جدا تعریف کنید. یک مشخصه برای کل تنظیمات کفایت نمی کنه؟
شبیه کاری که برای Student کردید.
یه مورد دیگه این بود که فرضا بجای اینکه بیام در متد مورد نظر در هر کلاس (متد با نام UpdateSettingRelatedProperties در اون فایل Visio) ، بعد از به روزرسانیِ مقادیر پروپرتی مورد نظر در اون کلاس ، بعدش بیام این مقادیر را در این متد ، در دیتابیس ذخیره کنم ، بجاش یه متدی در خودِ استراکچرِ تنظیمات بسازم و از اونجا در دیتابیس ذخیره کنم .
به این مسائل فکر کنید و تصمیم بگیرید. اولا وقتی تنظیمات رو ذخیره می کنه، معنی اش اینه که جزئی از Model ئه. ثانیا آیا این ساختار که ذخیره سازی اش در یک پایگاه داده یا فایل یا ... انجام میشه که ماهیت اش مجزا از اون ساختار ئه و در Model مشخص میشه، میتونه هر داده و پارامتری که برای ذخیره سازی لازم داره رو از داخل ساختار کسب کنه؟ پارامتری هست که باید مقدارش به متد داده بشه؟ شیء اون ساختار میدونه باید در کدوم پایگاه داده با چه تنظیماتی ذخیره اش کنه؟ یا میخواهید پارامتر های مورد نیاز برای اینکار رو به متد ارسال کنید؟ فرض کنید که اینکار رو انجام بدهید. آیا ViewModel مشخصات پایگاه داده رو میدونه و این اجازه رو داره که برای ذخیره سازی داده های ساختار متد با همچین پارامتر هایی فراخوانی کنه؟ احتمالا نه، چون بهش مربوط نیست و مشخصات پایگاه داده به Model مربوطه. اگر متدی که اینطور پارامتر هایی میخواد تعریف کنید، در دسترس ViewModel نیست و نباید باشه، چون پارامتر هایی میخواد که باید صرفا مختص روال داخلی Model باشه.
نظرتون در این رابطه چیه؟ در استراکچر تنظیمات ، متدی بسازم یا در همون کلاس (با متد UpdateSettingRelatedProperties) برای ذخیره سازی در دیتابیس ؟
نظر من اینه که در همون کلاس این کار را انجام بدم (همون روالی که در اون فایل Visio نوشتم) .
نظری ندارم. به این فکر کنید که روال این متد چه پارامتر های دیگری میخواد، چطور پارامتر های مورد نیاز رو کسب می کنه و کی و کجا و توسط کدوم لایه باید فراخوانی بشه.
 

SajjadKhati

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

میگم استاد ، وقتی فرضا پروپرتیِ Text ئه TextBox را به عنوان Target استفاده میکنیم و به پروپرتیِ دیگه Binding میکنیم ، اگه بعدا بیایم و مستقیما به پروپرتیِ Text ئه TextBox مون ، مقدار جدیدی بدیم ، خوب معلوم هست که مقدارِ شیِ Binding ای که قبلا برای این پروپرتی در نظر گرفته بودیم ، نادیده گرفته میشه و در نتیجه با تغییر مقدارِ پروپرتیِ Source مون ، دیگه مقدار پروپرتیِ Text ئه TextBox مون تغییری نمیکنه .

اما اگه کاربر ، با همین کنترل TextBox مون کنش داشته باشه ، یعنی اگه کاربر ، درونِ کادرِ مورد نظر در TextBox ، متن ای را بنویسه یا ویرایش کنه و در نتیجه ، TextBox ، درون کدهای خودش ، در اثر این تعامل ، خودش ، پروپرتیِ Text ئه TextBox اش را عوض کنه ، باعث نمیشه که Binding ای که به این پروپرتی (به عنوان Target) انجام داده بودیم ، نادیده گرفته بشه و دیگه Binding اش کار نکنه .

سئوال اینجاست که تفاوت این دو مقداردهی چیه که وقتی خودمون بصورت مستقیم ، پروپرتیِ Text ئه TextBox را تغییر بدیم ، Binding اش نادیده گرفته میشه اما اگه در اثر تعامل کاربر با کنترل TextBox ، مقدار پروپرتیِ Text ئه TextBox ، درون کدهای همون کنترل عوض بشه ، تاثیری در Binding نداره و نادیده گرفته نمیشه؟

چون در هر دو ، بالاخره مقداردهی پروپرتیِ Text ئه TextBox انجام میشه . پس علی الظاهر نباید فرقی در عملکردشون (برای قضیه ی Binding) داشته باشن .

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

بعد اینکه ذخیره ی تنظیمات مربوط به پروپرتی ای که در لایه ی View هست ، در دیتابیس را ، در همون لایه ی View ، متد و کلا عملیات مربوط به ذخیره سازیش در دیتابیس را انجام بدیم دیگه؟

چون طبق چیزی که گفتین هر لایه ، وظیفه ی مربوط به خودش را انجام بده تا در MVVM بشه هر لایه را منتقل یا جایگزین کرد ، اگه وظیفه ی ذخیره سازیِ تنظیماتی که مربوط به View میشه را ، به Model بسپاریم تا ذخیره کنه ، View مون ناقص میشه و بخشی از وظیفه ای که بهش مربوط میشه را انجام نداده و لایه ی دیگه ای براش انجام داده .
درست میگم؟

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

بعد اینکه استاد ، در کد زیر :

XML:
            <StackPanel Name="BackupSettingStackPanel" Orientation="Vertical"
            DataContext="{Binding RelativeSource={RelativeSource
            Mode=FindAncestor, AncestorType=local:PracticeWindow},
            Path=Drives}">

                <ComboBox Name="DriveComboBox" HorizontalAlignment="Left"
                 Margin="10"
                 Width="200" Height="28" IsSynchronizedWithCurrentItem="True"
                 ItemsSource="{Binding }"
                 ItemTemplate="{StaticResource Drive_DataTemplateKey}"/>

            </StackPanel>

من وقتی این ویندوز را اجرا میکنم ، همون زمان اجرای ویندوز ، در این کمبوباکسِ DriveComboBox ، آیتمِ ولش انتخاب میشه (بدون اینکه خودم مقدار پروپرتیِ SelectedIndex را مقداردهی کنم) .

اما وقتی همون DataContext را برای کمبوباکس تنظیم میکنم ، یعنی در کد زیر :

XML:
                <ComboBox Name="DriveComboBox" HorizontalAlignment="Left"
                 Margin="10"
                 Width="200" Height="28" IsSynchronizedWithCurrentItem="True"
                 DataContext="{Binding RelativeSource={RelativeSource
                 Mode=FindAncestor, AncestorType=local:PracticeWindow},
                 Path=Drives}"
                 ItemsSource="{Binding }"
                 ItemTemplate="{StaticResource Drive_DataTemplateKey}"/>

این مشکل وجود نداره . یعنی زمان اجرای ویندوز ، بصورت پیش فرض ، آیتمِ اولِ کمبوباکس ، انتخاب نمیشه .
میدونین علت این مشکل چیه؟

چرا بخاطر اینکه DataContext ، مستقیما روی خودِ ComboBox مقداردهی بشه یا نشه (یا بجاش روی والدش که StackPanel هست ، مقداردهی بشه که DataContext ئه ComboBox ، از والدش این مقدار را بگیره) ، روی پروپرتیِ SelectedIndex اش تاثیر میذاره؟

و اینکه نمیشه جوری بشه که DataContext ئه StackPanel (که والدش هست) را مقداردهی کنیم (و DataContext ئه کمبوباکس را مقداردهی نکنیم) اما این مشکل پیش نیاد؟

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

the_king

مدیرکل انجمن
سلامی مجدد
خیلی ممنون استاد از جواب خوب تون .

میگم استاد ، وقتی فرضا پروپرتیِ Text ئه TextBox را به عنوان Target استفاده میکنیم و به پروپرتیِ دیگه Binding میکنیم ، اگه بعدا بیایم و مستقیما به پروپرتیِ Text ئه TextBox مون ، مقدار جدیدی بدیم ، خوب معلوم هست که مقدارِ شیِ Binding ای که قبلا برای این پروپرتی در نظر گرفته بودیم ، نادیده گرفته میشه و در نتیجه با تغییر مقدارِ پروپرتیِ Source مون ، دیگه مقدار پروپرتیِ Text ئه TextBox مون تغییری نمیکنه .

اما اگه کاربر ، با همین کنترل TextBox مون کنش داشته باشه ، یعنی اگه کاربر ، درونِ کادرِ مورد نظر در TextBox ، متن ای را بنویسه یا ویرایش کنه و در نتیجه ، TextBox ، درون کدهای خودش ، در اثر این تعامل ، خودش ، پروپرتیِ Text ئه TextBox اش را عوض کنه ، باعث نمیشه که Binding ای که به این پروپرتی (به عنوان Target) انجام داده بودیم ، نادیده گرفته بشه و دیگه Binding اش کار نکنه .

سئوال اینجاست که تفاوت این دو مقداردهی چیه که وقتی خودمون بصورت مستقیم ، پروپرتیِ Text ئه TextBox را تغییر بدیم ، Binding اش نادیده گرفته میشه اما اگه در اثر تعامل کاربر با کنترل TextBox ، مقدار پروپرتیِ Text ئه TextBox ، درون کدهای همون کنترل عوض بشه ، تاثیری در Binding نداره و نادیده گرفته نمیشه؟

چون در هر دو ، بالاخره مقداردهی پروپرتیِ Text ئه TextBox انجام میشه . پس علی الظاهر نباید فرقی در عملکردشون (برای قضیه ی Binding داشته باشن) .
Binding به get و set مشخصه که نیست، به اون DependencyProperty است که ثبت شده، تغییر مقدار داخل کنترل هم تغییر مقدار منبعی است که get برمیگردونه. کاری با اون Binding نداره.
بعد اینکه ذخیره ی تنظیمات مربوط به پروپرتی ای که در لایه ی View هست ، در دیتابیس را ، در همون لایه ی View ، متد و کلا عملیات مربوط به ذخیره سازیش در دیتابیس را انجام بدیم دیگه؟
این معماری MVVM ئه؟ وظیفه لایه View صرفا تعامل با کاربر ئه. کاری با ذخیره سازی و واکشی داده نداره.
چون طبق چیزی که گفتین هر لایه ، وظیفه ی مربوط به خودش را انجام بده تا در MVVM بشه هر لایه را منتقل یا جایگزین کرد ، اگه وظیفه ی ذخیره سازیِ تنظیماتی که مربوط به View میشه را ، به Model بسپاریم تا ذخیره کنه ، View مون ناقص میشه و بخشی از وظیفه ای که بهش مربوط میشه را انجام نداده و لایه ی دیگه ای براش انجام داده .
شما میگید View وظیفه داره تنظیماتش رو ذخیره کنه، یعنی داده رو ذخیره کنه. کاری به این ندارید که در MVVM وظیفه View چیه و ذخیره سازی و واکشی داده وظیفه کدوم لایه است. در MVVM وظیفه هر لایه مشخص شده. View مگر قراره داده ذخیره کنه که حالا وظیفه اش باشه؟ ذخیره سازی داده جزئی از وظیفه تعامل با کاربر ئه؟ اگر View هم وظیفه Model رو انجام بده هم وظیفه View رو که دیگه View یک برنامه تک لایه مستقل ئه و مدل سه لایه نیست.
خیر. اصلا کاری با تعاریف MVVM ندارید. مثل کسی که تا حالا یک صفحه در مورد توصیف MVVM نخونده باشه.
===============

بعد اینکه استاد ، در کد زیر :

XML:
            <StackPanel Name="BackupSettingStackPanel" Orientation="Vertical"
            DataContext="{Binding RelativeSource={RelativeSource
            Mode=FindAncestor, AncestorType=local:PracticeWindow},
            Path=Drives}">

                <ComboBox Name="DriveComboBox" HorizontalAlignment="Left"
                 Margin="10"
                 Width="200" Height="28" IsSynchronizedWithCurrentItem="True"
                 ItemsSource="{Binding }"
                 ItemTemplate="{StaticResource Drive_DataTemplateKey}"/>

            </StackPanel>

من وقتی این ویندوز را اجرا میکنم ، همون زمان اجرای ویندوز ، در این کمبوباکسِ DriveComboBox ، آیتمِ ولش انتخاب میشه (بدون اینکه خودم مقدار پروپرتیِ SelectedIndex را مقداردهی کنم) .

اما وقتی همون DataContext را برای کمبوباکس تنظیم میکنم ، یعنی در کد زیر :

XML:
                <ComboBox Name="DriveComboBox" HorizontalAlignment="Left"
                 Margin="10"
                 Width="200" Height="28" IsSynchronizedWithCurrentItem="True"
                 DataContext="{Binding RelativeSource={RelativeSource
                 Mode=FindAncestor, AncestorType=local:PracticeWindow},
                 Path=Drives}"
                 ItemsSource="{Binding }"
                 ItemTemplate="{StaticResource Drive_DataTemplateKey}"/>

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

SajjadKhati

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

جریان اینه که با انتخاب کمبوباکس در اون پروژه (بنام DriveComboBox) ، یک کنترلِ چک باکس (بنام DriveAutoBackupingCheckBox) وجود داره که بعد از انتخاب نام درایو ، این چک باکس مشخص میکنه که پروپرتیِ AutoBackuping در اون درایو ، فعال هست یا نه .

حالا چون میخوام تا کاربر ، دکمه ی ok را در صفحه ی تنظیمات انتخاب نکرد (منظورم در پروژه ی اصلی هست نه در این پروژه ی خیلی کوچیک شده ی تمرینی) ، تغییراتِ تنظیماتی که انجام میده ، در پروپرتیِ مربوطه اِعمال نشه ، پس برای همه ی کنترل ها و مخصوصا برای کنترل هایی مثل چک باکس ( DriveAutoBackupingCheckBox) ، باید Binding ئه یک طرفه در نظر بگیرم (حالا نمیدونم راه دیگه ای داره یا نه) .

و همچنین از طرفی هم فرضا اگه پروپرتیِ AutoBackuping ئه مربوط به درایو D ، فعال باشه ، پس با انتخاب درایو D ، مقدارِ IsChecked ئه این چک باکس ، فعال میشه . اما حالا که برای همین درایو ، کاربر ، این گزینه را غیر فعال کنه (در این صورت انتظار داریم که IsChecked ئه این چک باکس برای درایوِ D که هر بارِ دیگه که از کمبوباکس انتخاب میشه _ تا زمانی که در همین شی از window هست و ازش خارج نشد _ غیر فعال باشه) و بعد بره در کمبوباکس ، درایو C را انتخاب کنه که فرضا مقدار AutoBackuping برای درایو C ، غیر فعال باشه .

حالا دوباره که از کمبوباکس ، درایو D را انتخاب کنه ، چون آخرین بار برای درایو D غیر فعال کرده بود ، انتظار داریم غیر فعال باشه اما من در این پروژه ، وقتی این کار را انجام میدم ، حتی با اونکه از CollectionViewSource استفاده کردم ، اما این مشکل حل نشد .

چون تا جایی که اشتباه نکنم ، CollectionView ها میتونن با سورس و کالکشنِ اصلی مون ارتباط داشته باشن بدون اینکه مقدارشون را تغییر بدن . درست میگم؟
کجای کد برای CollectionViewSource ام اشتباست که تغییرات (ئه AutoBackuping برای یک درایو) ، در اون ذخیره نمیشه؟
و کلا راهکار پیشنهادی تون برای این چیه؟


مشکل بعدیش هم اینه که وقتی از CollectionViewSource استفاده میکنم ، نمیدونم چرا بعضی وقت ها ، پروپرتی های Source ئه CollectionViewSource (در این پروژه ، از نوع InternalHard_DriveCollection هست) را میشناسه اما در بعضی مواقع دیگه نمیشناسه!
فرضا در Binding ای که برای چک باکسِ اولی به پروپرتیِ HasGoodState (ِکه درون کلاس InternalHard_DriveCollection هست) انجام شد ، نمیشناسه اما در Binding ای که برای چک باکسِ دومی به پروپرتیِ AutoBackuping (که درون کلاس Drive هست) ، میشناسه .
مشکل از کجاست؟

=====

یا فرضا به نظرتون چطوره بجای استفاده از CollectionView ، یه پروپرتی در View از نوع Object تعریف کنم که به پروپرتیِ مورد نظر در Model ، بایندینگ اش کنم و بعد این کنترل ها (مثل کمبوباکس و چک باکس و ...) را به این پروپرتی ای که در View (از نوع Object) تعریف کرده بودم ، متصل و Binding کنم؟
یعنی در 2 مرحله Binding کنم. چطوره؟


Binding به get و set مشخصه که نیست، به اون DependencyProperty است که ثبت شده، تغییر مقدار داخل کنترل هم تغییر مقدار منبعی است که get برمیگردونه. کاری با اون Binding نداره.

منظورتون اینه که اگه بجای مقداردهیِ مستقیمِ پروپرتی (ای که به عنوان Binding Target ازش استفاده کردیم) ، مستقیما به DependencyProperty ئه مربوط به اون پروپرتی مقدار بدیم ، دیگه مشکلی پیش نمیاد و شیِ Binding مون هم برای اون پروپرتی سر جاش میمونه؟

این معماری MVVM ئه؟ وظیفه لایه View صرفا تعامل با کاربر ئه. کاری با ذخیره سازی و واکشی داده نداره.

شما میگید View وظیفه داره تنظیماتش رو ذخیره کنه، یعنی داده رو ذخیره کنه. کاری به این ندارید که در MVVM وظیفه View چیه و ذخیره سازی و واکشی داده وظیفه کدوم لایه است. در MVVM وظیفه هر لایه مشخص شده. View مگر قراره داده ذخیره کنه که حالا وظیفه اش باشه؟ ذخیره سازی داده جزئی از وظیفه تعامل با کاربر ئه؟ اگر View هم وظیفه Model رو انجام بده هم وظیفه View رو که دیگه View یک برنامه تک لایه مستقل ئه و مدل سه لایه نیست.

خیر. اصلا کاری با تعاریف MVVM ندارید. مثل کسی که تا حالا یک صفحه در مورد توصیف MVVM نخونده باشه.

پس کلا هر نوع کار ذخیره سازی (حتی ذخیره سازیِ پروپرتی های مربوط به View) ، به لایه ی Model مربوط میشه .

من نمیدونم Drives چیه و شما کجا چه کد هایی نوشته اید ولی به چیزی که اینجا نوشتید ربطی نداره. در حالت دوم هم باید انتخابش میکرد.

در همین پروژه ای که پیوست کردم ، کل کدهای TabControl را پاک کنید و کد زیر را جایگزینش کنید ، نتیجه اش همونی میشه که گفتم . نمیدونم چرا :

XML:
        <TabControl HorizontalAlignment="Left" VerticalAlignment="Top"
        Margin="10">

            <TabItem Header="Tab 1">
                <Border  BorderThickness="1" BorderBrush="DeepSkyBlue" CornerRadius="10">

                    <StackPanel Name="BackupSettingStackPanel" Orientation="Vertical" Margin="10"
                    DataContext="{Binding RelativeSource={RelativeSource
                     Mode=FindAncestor, AncestorType=local:MainWindow},
                     Path=Drives}">

                        <CheckBox HorizontalAlignment="Left" Margin="10"
                        Content="Has Good State"
                        IsChecked="{Binding Path=HasGoodState, Mode=OneWay}"/>

                        <ComboBox Name="DriveComboBox" HorizontalAlignment="Left"
                          Margin="10"
                          Width="200" Height="28" IsSynchronizedWithCurrentItem="True"
                          ItemsSource="{Binding}"
                          ItemTemplate="{StaticResource Drive_DataTemplateKey}"/>

                        <CheckBox Name="DriveAutoBackupingCheckBox"
                          HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10"
                          Content="Auto Backuping"
                          IsEnabled="{Binding ElementName=DriveComboBox,
                         Path=SelectedIndex,
                         Converter={StaticResource AllBindingConverterKey},
                         ConverterParameter='DriveComboBox_SelectedIndex_CheckBox_IsEnabled_BindingConverter'}"
                        IsChecked="{Binding Path=AutoBackuping, Mode=OneWay}"/>

                    </StackPanel>

                </Border>
            </TabItem>

            <TabItem Header="Tab 2">
                <StackPanel>
                    <Button Margin="10" Content="Salam"></Button>
                    <CheckBox Margin="10" Content="Test"></CheckBox>
                </StackPanel>
            </TabItem>
        </TabControl>


تشکر استاد .
 

پیوست ها

  • SettingBinding.rar
    61.3 کیلوبایت · بازدیدها: 0
آخرین ویرایش:

the_king

مدیرکل انجمن
سلامی مجدد
خیلی ممنون استاد :rose:
استاد ، به پروژه ی کوچیکی که در این پست پیوست کردم ، بی زحمت نگاه کنید .

جریان اینه که با انتخاب کمبوباکس در اون پروژه (بنام DriveComboBox) ، یک کنترلِ چک باکس (بنام DriveAutoBackupingCheckBox) وجود داره که بعد از انتخاب نام درایو ، این چک باکس مشخص میکنه که پروپرتیِ AutoBackuping در اون درایو ، فعال هست یا نه .

حالا چون میخوام تا کاربر ، دکمه ی ok را در صفحه ی تنظیمات انتخاب نکرد (منظورم در پروژه ی اصلی هست نه در این پروژه ی خیلی کوچیک شده ی تمرینی) ، تغییراتِ تنظیماتی که انجام میده ، در پروپرتیِ مربوطه اِعمال نشه ، پس برای همه ی کنترل ها و مخصوصا برای کنترل هایی مثل چک باکس ( DriveAutoBackupingCheckBox) ، باید Binding ئه یک طرفه در نظر بگیرم (حالا نمیدونم راه دیگه ای داره یا نه) .

و همچنین از طرفی هم فرضا اگه پروپرتیِ AutoBackuping ئه مربوط به درایو D ، فعال باشه ، پس با انتخاب درایو D ، مقدارِ IsChecked ئه این چک باکس ، فعال میشه . اما حالا که برای همین درایو ، کاربر ، این گزینه را غیر فعال کنه (در این صورت انتظار داریم که IsChecked ئه این چک باکس برای درایوِ D که هر بارِ دیگه که از کمبوباکس انتخاب میشه _ تا زمانی که در همین شی از window هست و ازش خارج نشد _ غیر فعال باشه) و بعد بره در کمبوباکس ، درایو C را انتخاب کنه که فرضا مقدار AutoBackuping برای درایو C ، غیر فعال باشه .

حالا دوباره که از کمبوباکس ، درایو D را انتخاب کنه ، چون آخرین بار برای درایو D غیر فعال کرده بود ، انتظار داریم غیر فعال باشه اما من در این پروژه ، وقتی این کار را انجام میدم ، حتی با اونکه از CollectionViewSource استفاده کردم ، اما این مشکل حل نشد .

چون تا جایی که اشتباه نکنم ، CollectionView ها میتونن با سورس و کالکشنِ اصلی مون ارتباط داشته باشن بدون اینکه مقدارشون را تغییر بدن . درست میگم؟
کجای کد برای CollectionViewSource ام اشتباست که تغییرات (ئه AutoBackuping برای یک درایو) ، در اون ذخیره نمیشه؟
و کلا راهکار پیشنهادی تون برای این چیه؟
CollectionView این امکان رو میده که یک مجموعه مستقل از وضعیت منبع اش مرتب سازی بشه، یکسری اعضاء فیلتر بشن، جابجا بشن و ... فقط همین. با تغییر مقدار اعضاء یا اضافه کردن عضو و ... کاری نداره.
یک منبع داده دارید، یک کپی موقتی ازش میگیرید و در ویرایشگر کاربر داده موقتی رو ویرایش می کنه، نهایتا اگر تایید کرد، داده موقتی روی منبع داده اصلی ثبت میشه و اگر تایید نکرد داده موقتی دور ریخته میشه.
مشکل بعدیش هم اینه که وقتی از CollectionViewSource استفاده میکنم ، نمیدونم چرا بعضی وقت ها ، پروپرتی های Source ئه CollectionViewSource (در این پروژه ، از نوع InternalHard_DriveCollection هست) را میشناسه اما در بعضی مواقع دیگه نمیشناسه!
فرضا در Binding ای که برای چک باکسِ اولی به پروپرتیِ HasGoodState (ِکه درون کلاس InternalHard_DriveCollection هست) انجام شد ، نمیشناسه اما در Binding ای که برای چک باکسِ دومی به پروپرتیِ AutoBackuping (که درون کلاس Drive هست) ، میشناسه .
مشکل از کجاست؟
نمیدونم.
یا فرضا به نظرتون چطوره بجای استفاده از CollectionView ، یه پروپرتی در View از نوع Object تعریف کنم که به پروپرتیِ مورد نظر در Model ، بایندینگ اش کنم و بعد این کنترل ها (مثل کمبوباکس و چک باکس و ...) را به این پروپرتی ای که در View (از نوع Object) تعریف کرده بودم ، متصل و Binding کنم؟
یعنی در 2 مرحله Binding کنم. چطوره؟
متوجه نمیشم که کاربرد CollectionView در این مساله چی میتونه باشه.
منظورتون اینه که اگه بجای مقداردهیِ مستقیمِ پروپرتی (ای که به عنوان Binding Target ازش استفاده کردیم) ، مستقیما به DependencyProperty ئه مربوط به اون پروپرتی مقدار بدیم ، دیگه مشکلی پیش نمیاد و شیِ Binding مون هم برای اون پروپرتی سر جاش میمونه؟
نه. من نگفتم چیکار کنید، چیزی رو گفتم که اتفاق می افته. یک DependencyProperty ئه و DependencyProperty خواهد بود.
تصویر Binding رو ببینید :
databindingmostbasic.png
شما گفتید مشخصه Text یک TextBox رو Binding Target قرار داده اید، چیزی که گفتم اینه که شما مشخصه Text رو Binding Target قرار نداده اید،
با خود مشخصه نمی توانستید همچین کاری بکنید، چه بخواهید و چه نخواهید نمی توانید همچین کاری کرده باشید. اون چیزی که Binding Target ئه یک DependencyProperty بوده، با فیلد TextProperty داخل کلاس TextBox که اسم "Text" رو به ثبت می رسونه.
پس کلا هر نوع کار ذخیره سازی (حتی ذخیره سازیِ پروپرتی های مربوط به View) ، به لایه ی Model مربوط میشه .
صد البته، هر چیزی که داده محسوب بشه، چه تنظیمات و چه مورد دیگری. قبلا اشاره کردم که تنظیمات مورد خاص و ویژه ای نیست، فقط یکسری داده است.
در همین پروژه ای که پیوست کردم ، کل کدهای TabControl را پاک کنید و کد زیر را جایگزینش کنید ، نتیجه اش همونی میشه که گفتم . نمیدونم چرا :
صورت مساله رو کوچیک کنید. مگر به موقعیت DataContext در StackPanel و ComboBox مشکوک نیستید؟ بجای اینکه از سر و ته پروژه تون بزنید و سعی کنید کد اون رو کوچکتر کنید، اول یک مثال ساده کوچک بنویسید و ببینید آیا موقعیت DataContext نقشی خاصی در نتیجه داره یا نه.
اگر نداشت، می روید سراغ تحلیل مرحله به مرحله کدی که قبلا نوشته اید، به تدریج به مثال کوچک تون کدها رو اضافه می کنید تا ببینید کجا تاثیر میذاره.
XML:
<Window
        .
        .
        .
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:s="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.Resources>
            <x:Array x:Key="MyArray" Type="{x:Type s:String}">
                <s:String>One</s:String>
                <s:String>Two</s:String>
                <s:String>Three</s:String>
            </x:Array>
        </Grid.Resources>
        <StackPanel DataContext="{StaticResource MyArray}">
            <ComboBox IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}"/>
        </StackPanel>
    </Grid>
</Window>
XML:
<Window
        .
        .
        .
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:s="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.Resources>
            <x:Array x:Key="MyArray" Type="{x:Type s:String}">
                <s:String>One</s:String>
                <s:String>Two</s:String>
                <s:String>Three</s:String>
            </x:Array>
        </Grid.Resources>
        <StackPanel>
            <ComboBox DataContext="{StaticResource MyArray}" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}"/>
        </StackPanel>
    </Grid>
</Window>
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
CollectionView این امکان رو میده که یک مجموعه مستقل از وضعیت منبع اش مرتب سازی بشه، یکسری اعضاء فیلتر بشن، جابجا بشن و ... فقط همین. با تغییر مقدار اعضاء یا اضافه کردن عضو و ... کاری نداره.

نمیدونم.

متوجه نمیشم که کاربرد CollectionView در این مساله چی میتونه باشه.

سلامی مجدد
خیلی ممنون استاد .

آها . پس CollectionView ، باعث نمیشه که مقداری را بتونیم در Collection ای که به عنوان سورس و منبعِ CollectionView مون هست ، این اختیار را بده که توش مقداری را ذخیره کنیم یا نه .
بلکه صرفا برای فیلتر و گروه بندی و اینها ، این آزادی عمل را میده .
درسته؟

یک منبع داده دارید، یک کپی موقتی ازش میگیرید و در ویرایشگر کاربر داده موقتی رو ویرایش می کنه، نهایتا اگر تایید کرد، داده موقتی روی منبع داده اصلی ثبت میشه و اگر تایید نکرد داده موقتی دور ریخته میشه.

میدونم . ولی بجای این روش ، از Binding مایلم استفاده کنم .
در Binding ، اگه تغییراتی در مقدار پروپرتیِ سورس بعدا بوجود بیاد ، مثل حالت عادی نیست که نگران این باشیم که مقدار Target ئه مون را مجددا بصورت دستی ، به روزرسانی کنیم . و یا در رویدادهای مختلف دنبال آپدیت کردن Target ئه مون (مخصوصا در نسخه های بعدی) بگردیم .

میخوام این طور عمل کنم به عنوان راهکاری جایگزین :
دو تا پروپرتی بسازم . یکی در Model و یکی هم در View که هر دوشون از نوع ObservableCollection ئه جنریک هستند . منتها هر کدوم ، از نوعِ جنریک ای که در اون لایه تعریف میشه (بخاطر MVVM بودنش) که حالا با BindingConverter ، تبدیل بین شون انجام میشه .

اونی که در Model هست ، به عنوانِ سورس ئه یک طرفه به اون پروپرتی ای که در View هست ، Binding بشه .
اونی که در در View هست (که از نوع DependencyProperty هست و در کلاس تنظیمات تعریف میشه) ، مجددا به عنوان سورس ئه دو طرفه به پروپرتیِ مورد نظر در کنترل های مورد نظر (فرضا کنترل کمبوباکس و ...) Binding میشه .

مقادیری که تغییر داده شدن را جداگانه در tuple ها ذخیره میکنم (فیلدی که این tuple را نگه داری میکنه ، در کلاس تنظیمات تعریف میشه و همچنین مقادیر پیش فرض همه ی اعضای این tuple ها ، null هست) .

این روش چطوره؟

نه. من نگفتم چیکار کنید، چیزی رو گفتم که اتفاق می افته. یک DependencyProperty ئه و DependencyProperty خواهد بود.
تصویر Binding رو ببینید :
مشاهده پیوست 114978
شما گفتید مشخصه Text یک TextBox رو Binding Target قرار داده اید، چیزی که گفتم اینه که شما مشخصه Text رو Binding Target قرار نداده اید،
با خود مشخصه نمی توانستید همچین کاری بکنید، چه بخواهید و چه نخواهید نمی توانید همچین کاری کرده باشید. اون چیزی که Binding Target ئه یک DependencyProperty بوده، با فیلد TextProperty داخل کلاس TextBox که اسم "Text" رو به ثبت می رسونه.

صد البته، هر چیزی که داده محسوب بشه، چه تنظیمات و چه مورد دیگری. قبلا اشاره کردم که تنظیمات مورد خاص و ویژه ای نیست، فقط یکسری داده است.

متوجه شدم .
اگه مقدار اون پروپرتی (مقدارِ پروپرتیِ Binding Target) را با متد DependencyObject.SetValue تغییر بدیم ، باعث میشه که از اون به بعد ، مقدارِ Binding ای که به DependencyProperty مون اختصاص داده بودیم ، نادیده گرفته بشه (حذف بشه) .

چون در قسمت set ئه پروپرتی (ئه Binding Target) مون ، متدِ DependencyObject.SetValue را فراخونی میکنیم ، واسه ی همین با مقداردهیِ مستقیم به پروپرتی (ئه Binding Target) ، باعث نادیده گرفته شدنِ Binding میشیم .

اما اگه بجای اون متد ، توسط متدِ DependencyObject.SetCurrentValue مقداردهیِ پروپرتی (ئه Binding Target) را انجام بدیم ، شیِ Binding ای که به این پروپرتی اختصاص داده بودیم ، سر جاش باقی میمونه .

صورت مساله رو کوچیک کنید. مگر به موقعیت DataContext در StackPanel و ComboBox مشکوک نیستید؟ بجای اینکه از سر و ته پروژه تون بزنید و سعی کنید کد اون رو کوچکتر کنید، اول یک مثال ساده کوچک بنویسید و ببینید آیا موقعیت DataContext نقشی خاصی در نتیجه داره یا نه.
اگر نداشت، می روید سراغ تحلیل مرحله به مرحله کدی که قبلا نوشته اید، به تدریج به مثال کوچک تون کدها رو اضافه می کنید تا ببینید کجا تاثیر میذاره.
XML:
<Window
        .
        .
        .
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:s="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.Resources>
            <x:Array x:Key="MyArray" Type="{x:Type s:String}">
                <s:String>One</s:String>
                <s:String>Two</s:String>
                <s:String>Three</s:String>
            </x:Array>
        </Grid.Resources>
        <StackPanel DataContext="{StaticResource MyArray}">
            <ComboBox IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}"/>
        </StackPanel>
    </Grid>
</Window>
XML:
<Window
        .
        .
        .
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:s="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.Resources>
            <x:Array x:Key="MyArray" Type="{x:Type s:String}">
                <s:String>One</s:String>
                <s:String>Two</s:String>
                <s:String>Three</s:String>
            </x:Array>
        </Grid.Resources>
        <StackPanel>
            <ComboBox DataContext="{StaticResource MyArray}" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}"/>
        </StackPanel>
    </Grid>
</Window>

متوجه شدم .
توی این مثالی که زدید ، CollectionView ئه پیش فرض اش را که چک کردم ، مقدارِ پروپرتیِ CollectionView.CurrentPosition اش ، زمان اجرای ویندوز (در رویداد Loaded ئه ویندوز) برابر با صفر بود که یعنی اولین ایندکس (ایندکسِ صفر) که همون مقدار one بود ، انتخاب شده بود (یعنی مقدار پروپرتیِ CollectionView.CurrentItem اش برابر با رشته ی "one" بود) .

که با تغییر دادنِ این پروپرتی ها (CurrentPosition یا CurrentItem) ، این مشکل حل میشه (با قرار دادن ورودی متد CollectionView.MoveCurrentToPosition به مقدار 1- یا مقدار null برای متد MoveCurrentTo) :

C#:
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
    string[] myStrings = this.MainGrid.Resources["MyArray"] as string[];
    this.listCollectionView = CollectionViewSource.GetDefaultView(myStrings) as ListCollectionView;
    this.listCollectionView.MoveCurrentToPosition(-1);
    //this.listCollectionView.MoveCurrentTo(null);
}

مثال های من هم ، احتمالا باید همینطور باشن (هر چند اونها را تست نکردم) .
تشکر استاد .
 

the_king

مدیرکل انجمن
سلامی مجدد
خیلی ممنون استاد .

آها . پس CollectionView ، باعث نمیشه که مقداری را بتونیم در Collection ای که به عنوان سورس و منبعِ CollectionView مون هست ، این اختیار را بده که توش مقداری را ذخیره کنیم یا نه .
بلکه صرفا برای فیلتر و گروه بندی و اینها ، این آزادی عمل را میده .
درسته؟
بله.
میدونم . ولی بجای این روش ، از Binding مایلم استفاده کنم .
در Binding ، اگه تغییراتی در مقدار پروپرتیِ سورس بعدا بوجود بیاد ، مثل حالت عادی نیست که نگران این باشیم که مقدار Target ئه مون را مجددا بصورت دستی ، به روزرسانی کنیم . و یا در رویدادهای مختلف دنبال آپدیت کردن Target ئه مون (مخصوصا در نسخه های بعدی) بگردیم .
منافاتی با Binding که نداره، صرفا منبع Binding موقتی است، تغییر اش به معنای این نیست که حالا چون تغییر کرد باید در پایگاه داده ذخیره بشه.
میخوام این طور عمل کنم به عنوان راهکاری جایگزین :
دو تا پروپرتی بسازم . یکی در Model و یکی هم در View که هر دوشون از نوع ObservableCollection ئه جنریک هستند . منتها هر کدوم ، از نوعِ جنریک ای که در اون لایه تعریف میشه (بخاطر MVVM بودنش) که حالا با BindingConverter ، تبدیل بین شون انجام میشه .
ObservableCollection نقش اش در مساله چیه؟ از چه خاصیتی از ObservableCollection قراره استفاده کنید؟ به نظر میاد ObservableCollection رو بی هدف میخواهید استفاده کنید.
اونی که در Model هست ، به عنوانِ سورس ئه یک طرفه به اون پروپرتی ای که در View هست ، Binding بشه.
با این تفسیر داده ای که از Model میاد و یکطرفه است داده پیشفرض ئه، یعنی تنظیمات فعلی، تنظیماتی که کاربر هنوز ویرایش نکرده.
اونی که در در View هست (که از نوع DependencyProperty هست و در کلاس تنظیمات تعریف میشه) ، مجددا به عنوان سورس ئه دو طرفه به پروپرتیِ مورد نظر در کنترل های مورد نظر (فرضا کنترل کمبوباکس و ...) Binding میشه .
این داده ای است که کاربر در حین تغییر تنظیمات ایجاد می کنه، رخداد تغییرش نباید به روال ذخیره سازی متصل باشه. مادامی که View درخواست نکرده نباید ذخیره بشه.
مقادیری که تغییر داده شدن را جداگانه در tuple ها ذخیره میکنم (فیلدی که این tuple را نگه داری میکنه ، در کلاس تنظیمات تعریف میشه و همچنین مقادیر پیش فرض همه ی اعضای این tuple ها ، null هست) .
این روش چطوره؟
این بخش پیچیدگی اضافی ئه، تنظیمات یک مجموعه است، حالا ممکنه یکسری تغییرات به سایر داده ها وابسته باشن یا نباشن. چک کردن اینکه کدوم تنظیم تغییر کرده و کدوم نکرده بیشتر دردسر داره تا اعمال کلیه تنظیمات بدون توجه به تغییر کردن یا نکردن شون.
اما در خود View مقایسه یک به یک تنظیمات لحظه ای فعلی با تنظیمات پیشفرض (تنظیمات پیشفرض کارخانه ای برنامه) میتونه مفید باشه برای اینکه دکمه Default در فرم تنظیمات فعال باشه یا نه، که اگر تنظیمات فعلی تفاوتی با حالت پیشفرض نداشت، دکمه Default (که تنظیمات رو به حالت پیشفرض برمیگردونه) غیر فعال بشه، البته اگر همچین دکمه ای باشه.
 

SajjadKhati

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

منافاتی با Binding که نداره، صرفا منبع Binding موقتی است، تغییر اش به معنای این نیست که حالا چون تغییر کرد باید در پایگاه داده ذخیره بشه.

ObservableCollection نقش اش در مساله چیه؟ از چه خاصیتی از ObservableCollection قراره استفاده کنید؟ به نظر میاد ObservableCollection رو بی هدف میخواهید استفاده کنید.

سلامی مجدد استاد .
ببخشید که دیر شد و نشد جواب بدم (چون باید وقت بیشتری میذاشتم و موقع عید هم کارهای دیگه ای هم پیش اومد) :rose:

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

با این تفسیر داده ای که از Model میاد و یکطرفه است داده پیشفرض ئه، یعنی تنظیمات فعلی، تنظیماتی که کاربر هنوز ویرایش نکرده.

روی اینکه کاربر ، در این مرحله ، هنوز ویرایش کرده یا نکرده ، فکر نکردم . اما بصورت پیش فرض ، احتمالا همینطوره .

این داده ای است که کاربر در حین تغییر تنظیمات ایجاد می کنه، رخداد تغییرش نباید به روال ذخیره سازی متصل باشه. مادامی که View درخواست نکرده نباید ذخیره بشه.

بله همینطوره .

این بخش پیچیدگی اضافی ئه، تنظیمات یک مجموعه است، حالا ممکنه یکسری تغییرات به سایر داده ها وابسته باشن یا نباشن. چک کردن اینکه کدوم تنظیم تغییر کرده و کدوم نکرده بیشتر دردسر داره تا اعمال کلیه تنظیمات بدون توجه به تغییر کردن یا نکردن شون.
اما در خود View مقایسه یک به یک تنظیمات لحظه ای فعلی با تنظیمات پیشفرض (تنظیمات پیشفرض کارخانه ای برنامه) میتونه مفید باشه برای اینکه دکمه Default در فرم تنظیمات فعال باشه یا نه، که اگر تنظیمات فعلی تفاوتی با حالت پیشفرض نداشت، دکمه Default (که تنظیمات رو به حالت پیشفرض برمیگردونه) غیر فعال بشه، البته اگر همچین دکمه ای باشه.


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

روی دکمه ی "Default" هم فکر میکنم . تا حالا مد نظر نداشتم و فکر نکرده بود .

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

استاد ، در پروژه ای که در زیر پیوست میکنم (پروژه ای بسیار کوچیک و همون پروژه ی پست 945 با کمی تغییر هست) ، در رویداد MainWindow_OnLoaded ، وقتی برای پروپرتیِ Drives ، شی بسازم ، فقط همون موقع هست که Binding (بایندینگ با نام DrivesbindingSource که در کد سی شارپ در همون اول همین رویداد نوشتم) انجام میشه اما وقتی که به اعضای این پروپرتی (که اعضاش ، از نوع Drive هستن) (بعد از مقداردهیِ کالکشنِ این پروپرتی) ، مقدار بدیم ، دیگه شی Binding مطلع نمیشه . چرا؟

یعنی در کد این پروژه که بصورت زیر هست ، مشکلی نیست و شی Binding از تغییر مقدار پروپرتیِ Drives مطلع میشه :

C#:
            List<Drive> myDrives = new List<Drive>();
            myDrives.Add(new Drive("C", false, 0));
            myDrives.Add(new Drive("D", true, 100));
            myDrives.Add(new Drive("E", true, 73904));
            myDrives.Add(new Drive("F", false, -582));

            this.Drives = new InternalHard_DriveCollection(1000, true, myDrives);

اما وقتی بجای کد بالا ، بعد از مقداردهیِ پروپرتیِ Drives ، اعضاش را مقداردهی کنیم ، یعنی در کد زیر :

C#:
            this.Drives = new InternalHard_DriveCollection(1000, true);

            this.Drives.Add(new Drive("C", false, 0));
            this.Drives.Add(new Drive("D", true, 100));
            this.Drives.Add(new Drive("E", true, 73904));
            this.Drives.Add(new Drive("F", false, -582));

وقتی هر عضو از نوع Drive اضافه میشن ، دیگه به Binding اطلاع نمیده (فقط همون در خط اول که شی ای از Drives ساخته میشه اطلاع میده که تا اون زمان هنوز عضوی در کد بالا بهش تعلق نگرفته) .
دلیلش چیه؟

Drives که از نوع ObservableCollection هست . همه ی اعضای کلاس Drive در این پروژه (که پروپرتی های Name و AutoBackuping و AutoBackupingChangeValue هستن) هم که با پیاده سازی اینترفیس INotifyPropertyChanged ، رویداد PropertyChanged را اجرا کردن .
این دو که پیاده سازی بشن ، با تغییر اضافه شدن اعضای شیِ ObservableCollection ، باید Binding هم مطلع بشه (پروژه ی قبلی همین را تست کردم ولی نمیدونم اینجا چه مشکلی داره که نمیشه) .


پی نوشت : پروپرتیِ Drives از نوع InternalHard_DriveCollection هست که خودِ این نوع ، از نوع ObservableCollection<Drive> ارث بری میکنه .

خیلی ممنون استاد :rose:
 

پیوست ها

  • SettingBinding.rar
    75.5 کیلوبایت · بازدیدها: 1

the_king

مدیرکل انجمن
سلامی مجدد استاد .
ببخشید که دیر شد و نشد جواب بدم (چون باید وقت بیشتری میذاشتم و موقع عید هم کارهای دیگه ای هم پیش اومد) :rose:

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



روی اینکه کاربر ، در این مرحله ، هنوز ویرایش کرده یا نکرده ، فکر نکردم . اما بصورت پیش فرض ، احتمالا همینطوره .



بله همینطوره .




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

روی دکمه ی "Default" هم فکر میکنم . تا حالا مد نظر نداشتم و فکر نکرده بود .

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

استاد ، در پروژه ای که در زیر پیوست میکنم (پروژه ای بسیار کوچیک و همون پروژه ی پست 945 با کمی تغییر هست) ، در رویداد MainWindow_OnLoaded ، وقتی برای پروپرتیِ Drives ، شی بسازم ، فقط همون موقع هست که Binding (بایندینگ با نام DrivesbindingSource که در کد سی شارپ در همون اول همین رویداد نوشتم) انجام میشه اما وقتی که به اعضای این پروپرتی (که اعضاش ، از نوع Drive هستن) (بعد از مقداردهیِ کالکشنِ این پروپرتی) ، مقدار بدیم ، دیگه شی Binding مطلع نمیشه . چرا؟

یعنی در کد این پروژه که بصورت زیر هست ، مشکلی نیست و شی Binding از تغییر مقدار پروپرتیِ Drives مطلع میشه :

C#:
            List<Drive> myDrives = new List<Drive>();
            myDrives.Add(new Drive("C", false, 0));
            myDrives.Add(new Drive("D", true, 100));
            myDrives.Add(new Drive("E", true, 73904));
            myDrives.Add(new Drive("F", false, -582));

            this.Drives = new InternalHard_DriveCollection(1000, true, myDrives);

اما وقتی بجای کد بالا ، بعد از مقداردهیِ پروپرتیِ Drives ، اعضاش را مقداردهی کنیم ، یعنی در کد زیر :

C#:
            this.Drives = new InternalHard_DriveCollection(1000, true);

            this.Drives.Add(new Drive("C", false, 0));
            this.Drives.Add(new Drive("D", true, 100));
            this.Drives.Add(new Drive("E", true, 73904));
            this.Drives.Add(new Drive("F", false, -582));

وقتی هر عضو از نوع Drive اضافه میشن ، دیگه به Binding اطلاع نمیده (فقط همون در خط اول که شی ای از Drives ساخته میشه اطلاع میده که تا اون زمان هنوز عضوی در کد بالا بهش تعلق نگرفته) .
دلیلش چیه؟

Drives که از نوع ObservableCollection هست . همه ی اعضای کلاس Drive در این پروژه (که پروپرتی های Name و AutoBackuping و AutoBackupingChangeValue هستن) هم که با پیاده سازی اینترفیس INotifyPropertyChanged ، رویداد PropertyChanged را اجرا کردن .
این دو که پیاده سازی بشن ، با تغییر اضافه شدن اعضای شیِ ObservableCollection ، باید Binding هم مطلع بشه (پروژه ی قبلی همین را تست کردم ولی نمیدونم اینجا چه مشکلی داره که نمیشه) .


پی نوشت : پروپرتیِ Drives از نوع InternalHard_DriveCollection هست که خودِ این نوع ، از نوع ObservableCollection<Drive> ارث بری میکنه .

خیلی ممنون استاد :rose:
در این پروژه ای که پیوست کردید، منبع داده DriveComboBox چیه و کجا تعیین شده؟
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
در این پروژه ای که پیوست کردید، منبع داده DriveComboBox چیه و کجا تعیین شده؟

منبع داده اش را میخووستم به پروپرتیِ DriveForView بایندینگ کنم که این مشکل که پیش اومد ، اول اینو ببینم از کجاست و بعدا این کار را میکنم .

کلا هنوز منبع داده نداره .
تشکر استاد .
 

the_king

مدیرکل انجمن
منبع داده اش را میخووستم به پروپرتیِ DriveForView بایندینگ کنم که این مشکل که پیش اومد ، اول اینو ببینم از کجاست و بعدا این کار را میکنم .

کلا هنوز منبع داده نداره .
تشکر استاد .
مشکل اینجا است که شما دو تا رخداد متفاوت PropertyChanged و CollectionChanged رو مرتبط با هم فرض کرده اید و فرض رو بر این قرار داده اید که وقتی آیتمی به مجموعه اضافه شد، با توجه به اینکه CollectionChanged رخ داده، Binding تظاهر می کنه که PropertyChanged رخ داده، در حالی که اینها ربطی بهم ندارند، تغییر در داخل مجموعه برای Binding حکم تغییر مجموعه رو نداره، قبلا براتون توضیح داده بودم که مجموعه یک شیء ئه که با تغییر محتویاتش به شیء دیگری تبدیل نمیشه. اگر توضیحات ObservableCollection رو بخونید، نوشته که این ItemsControl ها هستند که از CollectionChanged استفاده می کنند تا از تغییر با خبر بشوند، وگرنه Binding شرایط بروز رسانی اش همون UpdateSourceTrigger های از پیش تعیین شده است.
این مثال رو ببینید :
BindingSample.rar
اینکه داخل مجموعه ObservableCollection تغییری رخ بده منجر به CollectionChanged میشه، ولی PropertyChangedCallback رو فراخوانی نمی کنه که Binding بخواد واکنشی نشون بده.
 

پیوست ها

  • BindingSample.rar
    28.5 کیلوبایت · بازدیدها: 3

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
مشکل اینجا است که شما دو تا رخداد متفاوت PropertyChanged و CollectionChanged رو مرتبط با هم فرض کرده اید و فرض رو بر این قرار داده اید که وقتی آیتمی به مجموعه اضافه شد، با توجه به اینکه CollectionChanged رخ داده، Binding تظاهر می کنه که PropertyChanged رخ داده، در حالی که اینها ربطی بهم ندارند،

سلامی مجدد استاد .
خیلی ممنون .

بله . وقتی اعضای کالکشن اضافه یا کم میشه ، CollectionChanged اتفاق میافته دیگه .
مثلا وقتی همون کد this.Drives.Add اجرا میشه و شی ای از Drive را بهش اضافه میکنیم ، این رویداد اتفاق میافته دیگه .
وقتی هم که اعضای درونِ شیِ Drive (مثل پروپرتی Name و AutoBackuping و ...) را تغییر بدیم ، رویداد PropertyChanged اش اتفاق میافته .
درسته؟

تغییر در داخل مجموعه برای Binding حکم تغییر مجموعه رو نداره، قبلا براتون توضیح داده بودم که مجموعه یک شیء ئه که با تغییر محتویاتش به شیء دیگری تبدیل نمیشه.

بله . میدونم .
یعنی تغییر مقدار پروپرتیِ Drives (که از نوع InternalHard_DriveCollection هست) ، ربطی به تغییر مقادیر اعضاش که از نوع Drive هستن ، نداره .
اگه منظورتون اینه ، اینها را میدونم .
ولی با این حال ، هنوز کامل ربط این مطالب را به مشکلم متوجه نشدم از کجاست . چون هنوز متوجه نشدم دلیل کار نکردن اون کد دوم چیه؟

اگر توضیحات ObservableCollection رو بخونید، نوشته که این ItemsControl ها هستند که از CollectionChanged استفاده می کنند تا از تغییر با خبر بشوند، وگرنه Binding شرایط بروز رسانی اش همون UpdateSourceTrigger های از پیش تعیین شده است.
این مثال رو ببینید :
BindingSample.rar
اینکه داخل مجموعه ObservableCollection تغییری رخ بده منجر به CollectionChanged میشه، ولی PropertyChangedCallback رو فراخوانی نمی کنه که Binding بخواد واکنشی نشون بده.

بله دیدم .
ممنون از پروژه تون .
باز ، دقیق ربط اش را به کار نکردن کد ام متوجه نشدم .
کلا میشه کاری کرد که اون کد دوم ام در پست 949 ، کار کنه؟ (چون شبیه اش در پروژه ی زیر که توضیح میدم ، کار کرد انگار) .

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

ببینید ، میگفتم قبلا این کار را انجام داده بودم ، تازه یادم اومد که توی پروژه ی MVVM Practices این کار را انجام دادم .
این پروژه را مجددا اینجا پیوست میکنم .
در کلاس StudentViewModel ، پروپرتی ای با نام StudentsForListView (که از نوع ObservableCollection<Student> هست) تعریف شده که به عنوان ItemsSource ئه ListView بایندینگ شده .
در متد سازنده ی StudentViewModel هم این پروپرتی را بصورت زیر مقداردهی کردم :

C#:
            this.StudentsForListView = new ObservableCollection<Student>();
            this.StudentsForListView.Add(new Student("Shahid Mahmood", "Kaveh"));
            this.StudentsForListView.Add(new Student("Shahid Ali Akbar", "Shirodi"));
            this.StudentsForListView.Add(new Student("Shahid Ahmad", "Keshvari"));
            this.StudentsForListView.Add(new Student("Shahid Edoardo", "Agnielli"));
            this.StudentsForListView.Add(new Student("Shahid Morteza", "Motahari"));
            this.StudentsForListView.Add(new Student("Shahid Mohsen", "Hojaji"));

یعنی بعد از شی دادن به کل کالکشن ، بعد از اون ، هر بار ، (شیِ) عضوی را به کالکشن اضافه میکردم .
نتیجه هم کاملا درست کار میکنه و همه ی اعضا در ListView اضافه شدن (و بایندینگ ئه ItemsSource ئه ListView ، به درستی انجام شد) .

خوب ، تفاوت این کد بالا در این پروژه ، با کدِ دومی که در پست 949 در اون پروژه دادم (یعنی کد زیر) :

C#:
            this.Drives = new InternalHard_DriveCollection(1000, true);

            this.Drives.Add(new Drive("C", false, 0));
            this.Drives.Add(new Drive("D", true, 100));
            this.Drives.Add(new Drive("E", true, 73904));
            this.Drives.Add(new Drive("F", false, -582));

تفاوت شون چیه که یکی کار میکنه و یکی دیگه نمیکنه؟

فرضا شاید بشه گفت که تفاوت ، شاید توی این باشه که در پروژه ی MVVM Practices ، پروپرتیِ StudentsForListView (که از نوع ObservableCollection<Student> و همچنین به عنوان Binding Source هست) ، متد RaisePropertyChanged را فراخونی میکنه اما در پروژه ی SettingBinding هم پروپرتیِ Drives و هم پروپرتیِ DrivesForView ، هر دوشون ، از نوع Dependency Property هستن که گفته بودین در این صورت ، لازم نیست که اینترفیس INotifyPropertyChanged را براش پیاده سازی کنیم .

غیر از این ، دیگه به چشمم ، تفاوت دیگه ای نمیان که داشته باشن .
پس مشکل از کجاست؟!

تشکر استاد .
 

پیوست ها

  • MVVM_Practies.rar
    1.9 مگایابت · بازدیدها: 0

the_king

مدیرکل انجمن
سلامی مجدد استاد .
خیلی ممنون .

بله . وقتی اعضای کالکشن اضافه یا کم میشه ، CollectionChanged اتفاق میافته دیگه .
مثلا وقتی همون کد this.Drives.Add اجرا میشه و شی ای از Drive را بهش اضافه میکنیم ، این رویداد اتفاق میافته دیگه .
بله، اما فقط CollectionChanged رخ میده، اتفاقی رخ نداده که بخاطرش Binding مقدار مشخصه رو تغییر بده و بخاطرش اون Converter شما عمل کنه و مقدار مشخصه DrivesForView تغییر کنه. فقط عضوی از Collection کم یا زیاد میشه، Collection به Collection دیگری تغییر نکرده، مشخصه ای که Collection رو داخلش قرار داده اید تغییر مقدار نداده، مشخصه وقتی تغییر می کنه که در Drives شیء جدیدی قرار بدهید که با قبلی متفاوته. برای همین PropertyChanged برای اون مشخصه Drives یا MyStrings1 رخ نداده و از دید Binding تغییری مشاهده نشده.
وقتی هم که اعضای درونِ شیِ Drive (مثل پروپرتی Name و AutoBackuping و ...) را تغییر بدیم ، رویداد PropertyChanged اش اتفاق میافته .
درسته؟
بله اما اگر مقدار مشخصه ای در داخل مجموعه MyStrings تغییر کنه و PropertyChanged هم برای اون مشخصه داخلی فراخوانی بشه، هر موجودیت خارجی که تمایل داشته باشه میتونه از اون PropertyChanged آگاه بشه ولی همچنان برای مشخصه MyStrings رخداد PropertyChanged فراخوانی نمیشه چون شیء MyStrings همونی است که بود. در اون پروژه نمونه که نوشته بودم، داخل کلاس MyStrings یک متد برای بروز رخداد تغییر مشخصه اضافه کنید :
C#:
        public void Changed()
        {
            OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(""));
        }
وقتی در اجرا ()MyStrings1.Changed رو فراخوانی می کنید، مقدار مشخصه MyStrings1 تغییر می کنه؟ Binding ای که MyStrings1 رو هدف گرفته واکنشی نشون میده؟ نه. یک مشخصه داخل MyStrings1 تغییر کرده، MyStrings1 که مقدار جدیدی نگرفته،همچنان همون شیء ئه که قبلا بود.
بله . میدونم .
یعنی تغییر مقدار پروپرتیِ Drives (که از نوع InternalHard_DriveCollection هست) ، ربطی به تغییر مقادیر اعضاش که از نوع Drive هستن ، نداره .
اگه منظورتون اینه ، اینها را میدونم .
خوب اگر این رو میدونید، بر چه اساسی توقع دارید با اضافه کردن عضو به Drives، اون Converter تون تغییری در مقدار DrivesForView بده؟ وقتی Binding اش تغییری مشاهده نکرده، چرا باید Converter اجرا بشه؟
ولی با این حال ، هنوز کامل ربط این مطالب را به مشکلم متوجه نشدم از کجاست . چون هنوز متوجه نشدم دلیل کار نکردن اون کد دوم چیه؟
به این دلیل که مادامی که مقدار Drives تغییر نکرده، Binding اش مقدار مقصد رو به روز نمی کنه، Converter عمل نمی کنه.
بله دیدم .
ممنون از پروژه تون .
باز ، دقیق ربط اش را به کار نکردن کد ام متوجه نشدم .
کلا میشه کاری کرد که اون کد دوم ام در پست 949 ، کار کنه؟ (چون شبیه اش در پروژه ی زیر که توضیح میدم ، کار کرد انگار) .

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

ببینید ، میگفتم قبلا این کار را انجام داده بودم ، تازه یادم اومد که توی پروژه ی MVVM Practices این کار را انجام دادم .
این پروژه را مجددا اینجا پیوست میکنم .
در کلاس StudentViewModel ، پروپرتی ای با نام StudentsForListView (که از نوع ObservableCollection<Student> هست) تعریف شده که به عنوان ItemsSource ئه ListView بایندینگ شده .
در متد سازنده ی StudentViewModel هم این پروپرتی را بصورت زیر مقداردهی کردم :

C#:
            this.StudentsForListView = new ObservableCollection<Student>();
            this.StudentsForListView.Add(new Student("Shahid Mahmood", "Kaveh"));
            this.StudentsForListView.Add(new Student("Shahid Ali Akbar", "Shirodi"));
            this.StudentsForListView.Add(new Student("Shahid Ahmad", "Keshvari"));
            this.StudentsForListView.Add(new Student("Shahid Edoardo", "Agnielli"));
            this.StudentsForListView.Add(new Student("Shahid Morteza", "Motahari"));
            this.StudentsForListView.Add(new Student("Shahid Mohsen", "Hojaji"));

یعنی بعد از شی دادن به کل کالکشن ، بعد از اون ، هر بار ، (شیِ) عضوی را به کالکشن اضافه میکردم .
نتیجه هم کاملا درست کار میکنه و همه ی اعضا در ListView اضافه شدن (و بایندینگ ئه ItemsSource ئه ListView ، به درستی انجام شد) .
عرض کردم خدمت تون، اون ListView است که برای اینکار کد داره، اون ListView اش که به رخداد های ObservableCollection متد متصل می کنه تا موقع تغییر داخل مجموعه با خبر بشه. ربطی به Binding نداره. Binding برایش چه اهمیتی داره که شما عضوی در مجموعه اضافه کرده اید یا خیر؟ شما یک شیء مجموعه در مشخصه MyStrings1 داشته اید و Binding مقدارش رو انتقال داده به مشخصه MyStrings2 و الان همون شیء در هر دو مشخصه قرار داره. هزار تا عضو هم به مجموعه اضافه کنید در هر دو مشخصه به همون شیء مجموعه دسترسی دارید، شیء که تغییر نمی کنه. حالا وقتی عضو جدیدی به MyStrings1 اضافه می کنید، نه مقدار شیء داخل MyStrings1 تغییری داشته و نه شیء داخل MyStrings2. اتفاقا همین الان اگر Binding هم از کار بیافته، اون اعضاء جدید در MyStrings2 هم قابل دسترسی هست، چون شیء مجموعه داخل MyStrings2 همانی است که در MyStrings1 بوده. الان Binding چرا باید کاری انجام بده؟ مگر شیء مبدا تغییر کرده؟ دوباره کاری کنه و همون مقداری که قبلا هم بوده مجددا در MyStrings2 قرار بده؟
خوب ، تفاوت این کد بالا در این پروژه ، با کدِ دومی که در پست 949 در اون پروژه دادم (یعنی کد زیر) :

C#:
            this.Drives = new InternalHard_DriveCollection(1000, true);

            this.Drives.Add(new Drive("C", false, 0));
            this.Drives.Add(new Drive("D", true, 100));
            this.Drives.Add(new Drive("E", true, 73904));
            this.Drives.Add(new Drive("F", false, -582));

تفاوت شون چیه که یکی کار میکنه و یکی دیگه نمیکنه؟
هر دوشون کار می کنه، شما به Drives عضو اضافه می کنید و اضافه میشه. اما آیا موجودیتی دارید که برایش اهمیت داشته باشه که عضوی به مجموعه Drives اضافه شده؟ برای Binding مهمه؟ نه. برای DrivesForView مهم ئه؟ نه. ListView برایش مهم بود چون میخواست تغییرات رو نشون بده. برای اینکار کد داشت. ظاهرا اینجا برای هیچ موجودیتی مهم نیست که شما داخل مجموعه Drives تغییری می دهید. هیچ جایی متدی به اون رخداد های مجموعه متصل نشده که با بروز CollectionChanged کار خاصی انجام بده.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
بله، اما فقط CollectionChanged رخ میده، اتفاقی رخ نداده که بخاطرش Binding مقدار مشخصه رو تغییر بده و بخاطرش اون Converter شما عمل کنه و مقدار مشخصه DrivesForView تغییر کنه. فقط عضوی از Collection کم یا زیاد میشه، Collection به Collection دیگری تغییر نکرده، مشخصه ای که Collection رو داخلش قرار داده اید تغییر مقدار نداده، مشخصه وقتی تغییر می کنه که در Drives شیء جدیدی قرار بدهید که با قبلی متفاوته. برای همین PropertyChanged برای اون مشخصه Drives یا MyStrings1 رخ نداده و از دید Binding تغییری مشاهده نشده.

بله اما اگر مقدار مشخصه ای در داخل مجموعه MyStrings تغییر کنه و PropertyChanged هم برای اون مشخصه داخلی فراخوانی بشه، هر موجودیت خارجی که تمایل داشته باشه میتونه از اون PropertyChanged آگاه بشه ولی همچنان برای مشخصه MyStrings رخداد PropertyChanged فراخوانی نمیشه چون شیء MyStrings همونی است که بود. در اون پروژه نمونه که نوشته بودم، داخل کلاس MyStrings یک متد برای بروز رخداد تغییر مشخصه اضافه کنید :
C#:
        public void Changed()
        {
            OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(""));
        }
وقتی در اجرا ()MyStrings1.Changed رو فراخوانی می کنید، مقدار مشخصه MyStrings1 تغییر می کنه؟ Binding ای که MyStrings1 رو هدف گرفته واکنشی نشون میده؟ نه. یک مشخصه داخل MyStrings1 تغییر کرده، MyStrings1 که مقدار جدیدی نگرفته،همچنان همون شیء ئه که قبلا بود.

خوب اگر این رو میدونید، بر چه اساسی توقع دارید با اضافه کردن عضو به Drives، اون Converter تون تغییری در مقدار DrivesForView بده؟ وقتی Binding اش تغییری مشاهده نکرده، چرا باید Converter اجرا بشه؟

به این دلیل که مادامی که مقدار Drives تغییر نکرده، Binding اش مقدار مقصد رو به روز نمی کنه، Converter عمل نمی کنه.

عرض کردم خدمت تون، اون ListView است که برای اینکار کد داره، اون ListView اش که به رخداد های ObservableCollection متد متصل می کنه تا موقع تغییر داخل مجموعه با خبر بشه. ربطی به Binding نداره. Binding برایش چه اهمیتی داره که شما عضوی در مجموعه اضافه کرده اید یا خیر؟ شما یک شیء مجموعه در مشخصه MyStrings1 داشته اید و Binding مقدارش رو انتقال داده به مشخصه MyStrings2 و الان همون شیء در هر دو مشخصه قرار داره. هزار تا عضو هم به مجموعه اضافه کنید در هر دو مشخصه به همون شیء مجموعه دسترسی دارید، شیء که تغییر نمی کنه. حالا وقتی عضو جدیدی به MyStrings1 اضافه می کنید، نه مقدار شیء داخل MyStrings1 تغییری داشته و نه شیء داخل MyStrings2. اتفاقا همین الان اگر Binding هم از کار بیافته، اون اعضاء جدید در MyStrings2 هم قابل دسترسی هست، چون شیء مجموعه داخل MyStrings2 همانی است که در MyStrings1 بوده. الان Binding چرا باید کاری انجام بده؟ مگر شیء مبدا تغییر کرده؟ دوباره کاری کنه و همون مقداری که قبلا هم بوده مجددا در MyStrings2 قرار بده؟

هر دوشون کار می کنه، شما به Drives عضو اضافه می کنید و اضافه میشه. اما آیا موجودیتی دارید که برایش اهمیت داشته باشه که عضوی به مجموعه Drives اضافه شده؟ برای Binding مهمه؟ نه. برای DrivesForView مهم ئه؟ نه. ListView برایش مهم بود چون میخواست تغییرات رو نشون بده. برای اینکار کد داشت. ظاهرا اینجا برای هیچ موجودیتی مهم نیست که شما داخل مجموعه Drives تغییری می دهید. هیچ جایی متدی به اون رخداد های مجموعه متصل نشده که با بروز CollectionChanged کار خاصی انجام بده.

سلامی مجدد
خیلی ممنون استاد .
یه چیزهایی دارم متوجه میشم اما کامل به جواب نرسیدم که چجوری حل اش کنم .
الان استاد ، طبق لینک زیر :


Occurs when an item is added, removed, or moved, or the entire list is refreshed.

رویداد CollectionChanged ، زمانی اتفاق میافته که عضوی به کالکشن اضافه یا حذف بشه یا کل کالکشن ، ریفرش بشه (نه اینکه شیِ کلِ کالکشن ، تغییر کنه . بلکه مثلا متد Clear فراخونی بشه و ...) . درسته؟

بنابراین من متوجه شدم همونطور که گفتید که ListView (در جریان اون پروژه) ، برای انجام کارش ، هندلرِ متدش را به رویداد (ئه CollectionChanged) متصل میکنه ، پس من هم برای اینکه متوجه بشم که چه وقتی ، به پروپرتیِ Drives ام ، عضوی اضافه میکنم ، متدی را به این رویداد متصل کنم (هر چند لازم به استفاده از این رویداد هم نیست چون خودم دارم در همون کلاس ، کدِ اضافه کردنِ اعضاش را مینویسم دیگه) .

به هر حال ، پس تا اینجا ، کدم به این شکل زیر (در پروژه ی SettingBinding) میشه :

C#:
        private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
        {
            Binding DrivesbindingSource = new Binding("Drives");
            DrivesbindingSource.Source = this;
            DrivesbindingSource.Mode = BindingMode.OneWay;
            DrivesbindingSource.Converter = this.Resources["AllBindingConverterKey"] as AllBindingConverter;
            DrivesbindingSource.ConverterParameter = "MainWindow_Drives_MainWindow_DrivesForView_BindingConverter";
            this.SetBinding(DrivesForViewProperty, DrivesbindingSource);

            this.Drives = new InternalHard_DriveCollection(1000, true);
            this.Drives.CollectionChanged += new NotifyCollectionChangedEventHandler(this.Drives_CollectionChanged);
            this.Drives.Add(new Drive("C", false, 0));
            this.Drives.Add(new Drive("D", true, 100));
            this.Drives.Add(new Drive("E", true, 73904));
            this.Drives.Add(new Drive("F", false, -582));

        }

        private void Drives_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {

        }

اما حالا مشکل اینه که حالا در متد هندلرِ Drives_CollectionChanged که متوجه شدم چه آیتمی ، به کالکشن ئه Drives ، حذف یا اضافه شده ، حالا در این متد ، چه کدی بنویسم تا این آیتم را به اون شیِ Binding (شیِ DrivesbindingSource) متوجه کنم تا به Converter و بعد هم به Target Biding Property اش که همون DrivesForViewProperty هست ، منتقل کنه تا اون هم ، همین مقدار را برای خودش در نظر بگیره و آپدیت بشه؟

اگه این کار شدنی هست ، پس چجوری میشه با کد سی شارپ انجام داد ؟
اگه شدنی نیست ، پس کنترل ListView چجوری این کار را انجام داد؟
تشکر استاد .
 

the_king

مدیرکل انجمن
سلامی مجدد
خیلی ممنون استاد .
یه چیزهایی دارم متوجه میشم اما کامل به جواب نرسیدم که چجوری حل اش کنم .
الان استاد ، طبق لینک زیر :




رویداد CollectionChanged ، زمانی اتفاق میافته که عضوی به کالکشن اضافه یا حذف بشه یا کل کالکشن ، ریفرش بشه (نه اینکه شیِ کلِ کالکشن ، تغییر کنه . بلکه مثلا متد Clear فراخونی بشه و ...) . درسته؟
حرف عجیبی می زنید، تغییر شیء کل کالکشن یعنی چی؟ تا حالا در #C شی (از هر نوع کلاسی که مد نظرتون باشه) دیده اید که بتونه به شیء دیگری تغییر کنه؟
اما حالا مشکل اینه که حالا در متد هندلرِ Drives_CollectionChanged که متوجه شدم چه آیتمی ، به کالکشن ئه Drives ، حذف یا اضافه شده ، حالا در این متد ، چه کدی بنویسم تا این آیتم را به اون شیِ Binding (شیِ DrivesbindingSource) متوجه کنم تا به Converter و بعد هم به Target Biding Property اش که همون DrivesForViewProperty هست ، منتقل کنه تا اون هم ، همین مقدار را برای خودش در نظر بگیره و آپدیت بشه؟
در اون پروژه مثالش رو نوشته بودم.
اگه شدنی نیست ، پس کنترل ListView چجوری این کار را انجام داد؟
ListView چنین کاری نکرده ها، ListView هم با اون Binding و Converter شما همین مشکل رو خواهد داشت. ListView کاری به اینکه داده از Binding میاد یا نمیاد نداره، یک ObservableCollection تحویل گرفته و حواسش به رخداد های اون ئه. وقتی شما در Converter یک داده جدید از نوع ObservableCollection یا هر نوع دیگری ایجاد می کنید، چیزی که به ListView تحویل داده میشه همون داده است، دیگه ارتباطی با منبع اصلی نداره. اگر رخداد CollectionChanged در اون مجموعه جدیدتون رخ نمیده تقصیر ListView که نیست.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
حرف عجیبی می زنید، تغییر شیء کل کالکشن یعنی چی؟ تا حالا در #C شی (از هر نوع کلاسی که مد نظرتون باشه) دیده اید که بتونه به شیء دیگری تغییر کنه؟

سلامی مجدد استاد
خیلی ممنون

منظورم تغییر مقدار شیِ کلِ کالکشن هست .
منظورم اعضای کالکشن نیست .

در اون پروژه مثالش رو نوشته بودم.

راستی استاد ، تازه حواسم جمع شد .
کد شما چه تفاوتی داره که وقتی دکمه ی "Binding MyStrings2 < MyStrings1" را میزنیم (که MyStrings2 را به MyStrings1 بایندینگ میکنه) (و هر دوشون از نوع ObservableCollection هم هستن) ، بعدش که دکمه ی "Add Item" را میزنیم که در نتیجه به پروپرتیِ MyStrings1 اضافه میکنه ، بعد از اون ، پروپرتیِ MyStrings2 هم از تغییر مقدار و اضافه شدن اعضاش با خبر میشن و در ListBox ئه دومی ، نمایش داده میشن؟!!

من تازه حواسم به این قضیه جمع شد .
پس در کد من ، چرا DrivesForView ، از اضافه شدنِ اعضای Drives ، چیزی متوجه نمیشه و بهش اضافه نمیشه .
من همه ی کدتون را بررسی کردم اما متوجه نشدم تفاوت کد من و شما کجاست که این اختلاف را داره؟!

واسه ی من فقط همین تیکه ی با خبر شدن از تغییر اعضای کالکشن (در بایندینگ) مهم هست .

ListView چنین کاری نکرده ها، ListView هم با اون Binding و Converter شما همین مشکل رو خواهد داشت. ListView کاری به اینکه داده از Binding میاد یا نمیاد نداره، یک ObservableCollection تحویل گرفته و حواسش به رخداد های اون ئه. وقتی شما در Converter یک داده جدید از نوع ObservableCollection یا هر نوع دیگری ایجاد می کنید، چیزی که به ListView تحویل داده میشه همون داده است، دیگه ارتباطی با منبع اصلی نداره. اگر رخداد CollectionChanged در اون مجموعه جدیدتون رخ نمیده تقصیر ListView که نیست.

تشکر استاد .
 

the_king

مدیرکل انجمن
سلامی مجدد استاد
خیلی ممنون

منظورم تغییر مقدار شیِ کلِ کالکشن هست .
منظورم اعضای کالکشن نیست .
خوب این یعنی چی؟ شما یک مثال بزنید از نوع کلاس دلخواه تون که مقدار شیء کل اش به فلان طریق تغییر می کنه.
راستی استاد ، تازه حواسم جمع شد .
ظاهرا نشده، هیچ اشاره ای به myStrings1_CollectionChanged نمی کنید.
کد شما چه تفاوتی داره که وقتی دکمه ی "Binding MyStrings2 < MyStrings1" را میزنیم (که MyStrings2 را به MyStrings1 بایندینگ میکنه) (و هر دوشون از نوع ObservableCollection هم هستن) ، بعدش که دکمه ی "Add Item" را میزنیم که در نتیجه به پروپرتیِ MyStrings1 اضافه میکنه ، بعد از اون ، پروپرتیِ MyStrings2 هم از تغییر مقدار و اضافه شدن اعضاش با خبر میشن و در ListBox ئه دومی ، نمایش داده میشن؟!!
چرا به تفاوت بین Binding MyStrings2 < MyStrings1 و Binding MyStrings2 < MyStrings1 + Converter توجه نمی کنید؟
MyStrings2 که از تغییرات با خبر نمیشه، چون تغییری در شیء داخل مشخصه وجود نداره، میدونیم که همون شیء که در MyStrings1 هست از موقع شروع Binding در MyStrings2 هم هست، اضافه شدن آیتم به مجموعه هم موجب تغییری در این وضعیت نمی شه. ListBox ها از تغییر با خبر میشن چون به ObservableCollection ای که تحویل میگیرند رخداد متصل می کنند و با اضافه شدن هر آیتم رخداد ایجاد میشه، به Binding شون ربطی نداره.
من تازه حواسم به این قضیه جمع شد .
پس در کد من ، چرا DrivesForView ، از اضافه شدنِ اعضای Drives ، چیزی متوجه نمیشه و بهش اضافه نمیشه .
من همه ی کدتون را بررسی کردم اما متوجه نشدم تفاوت کد من و شما کجاست که این اختلاف را داره؟!
دوباره برگردید پست ها رو از نو بخونید، DrivesForView داره با Binding تغذیه میشه، Binding قبلا مقدار Drives رو به Converter انتقال داده، حالا وقتی عضو جدیدی به Drives اضافه شد با Source جدیدی روبرو شده که بخواد مجددا به Converter منتقلش کنه؟ نه. این شیء در Drives همونه که قبلا بود. از دید Binding تغییری رخ نداده.
اگر همه کد رو بررسی می کردید، متوجه می شدید که پس از Binding MyStrings2 < MyStrings1 + Converter با اجرا کردن Add Item مشخصه MyStrings2 به روز نمیشده (همانطور که انتظار میرفت)، اما بعد از یکبار اجرای CollectionChanged > PropertyChanged
(حالا با اون روش متغیر واسطه temp که مشخصه رو وادار به تغییر مقدار می کنه یا با روش دیگری فرضا ()GetBindingExpression(MyStrings2Property)?.UpdateTarget ) اون هم به روز میشه.
واسه ی من فقط همین تیکه ی با خبر شدن از تغییر اعضای کالکشن (در بایندینگ) مهم هست .
برای بار چندم میگم، از دید اون مشخصه و از دید Binding تغییری رخ نداده، مقدار جدیدی به مشخصه Drives داده نشده که تغییری رخ داده باشه. وقتی PropertyChanged رخ نداده، Binding کاری لازم نمی بینه که بکنه. Binding هیچ کاری با اضافه شدن اعضاء انجام نمیده.
 

SajjadKhati

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

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

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

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


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

اما برای پروپرتی هایی که خودمون بصورت دستی به عنوان Binding Target Property ساختیم ، این عمل بصورت اتوماتیک اتفاق نمیافته و در صورت اضافه کردن آیتم به پروپرتی کالکشن Source ، آیتمی به پروپرتی کالکشن Target مون ، اضافه نمیشه .

در این صورت خودمون باید بصورت دستی از رویداد CollectionChanged ئه ObsevableCollection استفاده کنیم و در هندلرِ این رویداد ، Binding Target Property را با فراخونیِ متدِ UpdateTarget ، وادار به ، به روزرسانی کنیم (بصورت زیر) :
توسط متدِ
FrameworkElement.GetBindingExpression یا توسط متد استاتیک BindingOperations.GetBindingExpression ، پروپرتیِ Binding Target Property مون (در اینجا مثلا DrivesForViewProperty) را میدیم . متد UpdateTarget را فراخونی میکنیم :

C#:
        private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
        {
            Binding DrivesbindingSource = new Binding("Drives");
            DrivesbindingSource.Source = this;
            DrivesbindingSource.Mode = BindingMode.OneWay;
            DrivesbindingSource.Converter = this.Resources["AllBindingConverterKey"] as AllBindingConverter;
            DrivesbindingSource.ConverterParameter = "MainWindow_Drives_MainWindow_DrivesForView_BindingConverter";
            this.SetBinding(DrivesForViewProperty, DrivesbindingSource);

            this.Drives = new InternalHard_DriveCollection(1000, true);
            this.Drives.CollectionChanged += new NotifyCollectionChangedEventHandler(this.Drives_CollectionChanged);
            this.Drives.Add(new Drive("C", false, 0));
            this.Drives.Add(new Drive("D", true, 100));
            this.Drives.Add(new Drive("E", true, 73904));
            this.Drives.Add(new Drive("F", false, -582));

            ListCollectionView drivesForViewDefaultCollectionView = CollectionViewSource.GetDefaultView(this.DrivesForView) as ListCollectionView;
            drivesForViewDefaultCollectionView?.MoveCurrentToPosition(-1);
        }

        private void Drives_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            this.GetBindingExpression(DrivesForViewProperty).UpdateTarget();
        }

رویاد CollectionChanged ، وقتی فراخونی میشه که آیتمی به مجموعه ی کالکشن ، اضافه یا حذف یا کل کالکشن ، ریفرش بشه (فرضا با متد Clear در کالکشن ، کل کالکشن ریفرش بشه) .

درسته؟

خوب این یعنی چی؟ شما یک مثال بزنید از نوع کلاس دلخواه تون که مقدار شیء کل اش به فلان طریق تغییر می کنه.

تقریبا در بالا توضیح دادم استاد .
مثلا کد زیر :

C#:
this.Drives = new InternalHard_DriveCollection(1000, true);

این ، یک شی از کل کالکشن هست .
وقتی دوباره تکرار بشه ، یعنی در کد زیر :

C#:
this.Drives = new InternalHard_DriveCollection(1000, true);

مجددا شیِ جدیدِ دیگه ای (برای کل کالکشن) در نظر گرفته شده .
منظورم شیِ و آیتمِ جدید دادن بهشون نبود . بلکه تغییر شیِ کل کالکشن که در بالا گفتم .
حالا بماند .

ظاهرا نشده، هیچ اشاره ای به myStrings1_CollectionChanged نمی کنید.

چرا به تفاوت بین Binding MyStrings2 < MyStrings1 و Binding MyStrings2 < MyStrings1 + Converter توجه نمی کنید؟
MyStrings2 که از تغییرات با خبر نمیشه، چون تغییری در شیء داخل مشخصه وجود نداره، میدونیم که همون شیء که در MyStrings1 هست از موقع شروع Binding در MyStrings2 هم هست، اضافه شدن آیتم به مجموعه هم موجب تغییری در این وضعیت نمی شه. ListBox ها از تغییر با خبر میشن چون به ObservableCollection ای که تحویل میگیرند رخداد متصل می کنند و با اضافه شدن هر آیتم رخداد ایجاد میشه، به Binding شون ربطی نداره.

دوباره برگردید پست ها رو از نو بخونید، DrivesForView داره با Binding تغذیه میشه، Binding قبلا مقدار Drives رو به Converter انتقال داده، حالا وقتی عضو جدیدی به Drives اضافه شد با Source جدیدی روبرو شده که بخواد مجددا به Converter منتقلش کنه؟ نه. این شیء در Drives همونه که قبلا بود. از دید Binding تغییری رخ نداده.
اگر همه کد رو بررسی می کردید، متوجه می شدید که پس از Binding MyStrings2 < MyStrings1 + Converter با اجرا کردن Add Item مشخصه MyStrings2 به روز نمیشده (همانطور که انتظار میرفت)، اما بعد از یکبار اجرای CollectionChanged > PropertyChanged
(حالا با اون روش متغیر واسطه temp که مشخصه رو وادار به تغییر مقدار می کنه یا با روش دیگری فرضا ()GetBindingExpression(MyStrings2Property)?.UpdateTarget ) اون هم به روز میشه.

برای بار چندم میگم، از دید اون مشخصه و از دید Binding تغییری رخ نداده، مقدار جدیدی به مشخصه Drives داده نشده که تغییری رخ داده باشه. وقتی PropertyChanged رخ نداده، Binding کاری لازم نمی بینه که بکنه. Binding هیچ کاری با اضافه شدن اعضاء انجام نمیده.

خیلی ممنون استاد
از این توضیحات تون و توضیحات پست های قبل تون ، نتیجه ی بالا را که توضیح دادم ، گرفتم . بررسی میکنید که درسته؟
:rose:
 
آخرین ویرایش:

the_king

مدیرکل انجمن
سلامی مجدد استاد .
خیلی ممنون . حالا متوجه شدم .
استاد ، ببینید چیزهایی که میگم ، درسته؟ :

در Binding ، فقط اگه شیِ اون پروپرتی عوض بشه ، شیِ بایندینگ ، به Binding Target Property اش اطلاع میده .
در این مثال Binding که در موردش صحبت می کنیم درسته ولی نه تنها حالت اش. حالت کلی اش اون UpdateSourceTrigger ها است که بهش اشاره کردم.
یعنی فرضا اگه پروپرتیِ HasGoodState را به عنوان Binding Source Property استفاده میکنیم ، اگه مقدارش تغییر کنه ، به Binding Target Property اش اطلاع و باعث تغییر مقدارش میشه .
بله.
برای کالکشن ها ، باز هم اگه فقط شیِ کالکشن عوض بشه (نه اینکه آیتمی بهش اضافه یا حذف بشه) ، شیِ بایندینگ ، به Binding Target Property اش بصورت اتوماتیک اطلاع میده .
طبعا اگر Binding ئه بر اساس PropertyChanged عمل می کنه همینطوره.
یعنی اگه پروپرتیِ Drives را که به عنوان Binding Source Property استفاده میکنیم و به پروپرتیِ DrivesForView (به عنوان Binding Target Property) ، بایندینگ میکنیم ، در اینجا فقط اگه مقدار Drives تغییر کنه (نه اینکه عضوی را بهش اضافه و حذف کنیم) ، در این صورت فقط اتوماتیک به پروپرتیِ DrivesForView اطلاع و مقدارش را تغییر میده .
بله.
اما اگه وقتی عضو و آیتمی را که به کالکشن مون (به عنوان Binding Source Property مون) اضافه میکنیم ، حتی اگه از ObsevableCollection هم استفاده کنیم ، در این صورت ، بایندینگ ، آیتم اضافه شده را به Binding Target Property منتقل نمیکنه و باعث آپدیت آیتم هاش بصورت اتوماتیک نمیشه (البته اگه Binding Target Property مون ، پروپرتیِ مربوط به کنترل باشه ، چون از رویداد CollectionChanged ئه ObsevableCollection استفاده میکنه ، بله ، بصورت اتوماتیک آیتم های Binding Target Property را آپدیت میکنه) .
توضیح تون ایراد داره، شما Binding رو در رخداد CollectionChanged دخیل کرده اید، در حالی که قبلا عرض کردم که Binding نقشی در رخداد CollectionChanged نداره. اگر Binding ای هم وجود نداشت همین مساله برای رابطه ObsevableCollection و کنترل رخ میداد.
Binding قبلا شیء ObsevableCollection یا هر نوع داده دیگری که منبع بوده به کنترل تحویل داده، دیگه اینکه بعدا در هنگام تغییرات داخلی اون شیء چه رخداد هایی منجر به بروز رسانی در کنترل میشه نه ربطی به Binding داره و نه واسطه این ارتباط ئه و نه در بروز شون نقشی داره و نه از کار افتادن Binding تاثیری در این بروز رسانی بخاطر تغییر داخلی شیء میذاره.
اما برای پروپرتی هایی که خودمون بصورت دستی به عنوان Binding Target Property ساختیم ، این عمل بصورت اتوماتیک اتفاق نمیافته و در صورت اضافه کردن آیتم به پروپرتی کالکشن Source ، آیتمی به پروپرتی کالکشن Target مون ، اضافه نمیشه .
درست نیست. ایراد به واسطه Converter شما است نه Binding دستی. فرض کنید که مقدار مشخصه X با Binding به مشخصه Y انتقال داده میشه، نتیجه اش اینه که شیء داخل X که مبداء هست در مقصد که Y ئه قابل دسترسی است، یک ارجاع ساده به شیء و طبعا Y به همون شیء اشاره داره که X. و اگر این شیء یک مجموعه باشه، اضافه کردن آیتم در X هیچ فرقی با اضافه کردن آیتم در Y نداره، هر دوشون به یک شیء اشاره دارند، اگر X چهار عضو داشته باشه، یعنی Y چهار عضو داره. محال ئه که در مجموعه داخل X تغییری رخ بده در حالی که Y (به همون شیء اشاره می کنه) بدون بروز رسانی مونده باشه.
در این صورت خودمون باید بصورت دستی از رویداد CollectionChanged ئه ObsevableCollection استفاده کنیم و در هندلرِ این رویداد ، Binding Target Property را با فراخونیِ متدِ UpdateTarget ، وادار به ، به روزرسانی کنیم (بصورت زیر) :
توسط متدِ
FrameworkElement.GetBindingExpression یا توسط متد استاتیک BindingOperations.GetBindingExpression ، پروپرتیِ Binding Target Property مون (در اینجا مثلا DrivesForViewProperty) را میدیم . متد UpdateTarget را فراخونی میکنیم :

C#:
        private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
        {
            Binding DrivesbindingSource = new Binding("Drives");
            DrivesbindingSource.Source = this;
            DrivesbindingSource.Mode = BindingMode.OneWay;
            DrivesbindingSource.Converter = this.Resources["AllBindingConverterKey"] as AllBindingConverter;
            DrivesbindingSource.ConverterParameter = "MainWindow_Drives_MainWindow_DrivesForView_BindingConverter";
            this.SetBinding(DrivesForViewProperty, DrivesbindingSource);

            this.Drives = new InternalHard_DriveCollection(1000, true);
            this.Drives.CollectionChanged += new NotifyCollectionChangedEventHandler(this.Drives_CollectionChanged);
            this.Drives.Add(new Drive("C", false, 0));
            this.Drives.Add(new Drive("D", true, 100));
            this.Drives.Add(new Drive("E", true, 73904));
            this.Drives.Add(new Drive("F", false, -582));

            ListCollectionView drivesForViewDefaultCollectionView = CollectionViewSource.GetDefaultView(this.DrivesForView) as ListCollectionView;
            drivesForViewDefaultCollectionView?.MoveCurrentToPosition(-1);
        }

        private void Drives_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            this.GetBindingExpression(DrivesForViewProperty).UpdateTarget();
        }

رویاد CollectionChanged ، وقتی فراخونی میشه که آیتمی به مجموعه ی کالکشن ، اضافه یا حذف یا کل کالکشن ، ریفرش بشه (فرضا با متد Clear در کالکشن ، کل کالکشن ریفرش بشه) .

درسته؟
بله.
تقریبا در بالا توضیح دادم استاد .
مثلا کد زیر :

C#:
this.Drives = new InternalHard_DriveCollection(1000, true);

این ، یک شی از کل کالکشن هست .
وقتی دوباره تکرار بشه ، یعنی در کد زیر :

C#:
this.Drives = new InternalHard_DriveCollection(1000, true);

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

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

بالا