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

the_king

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

SajjadKhati

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

یا اینکه راهی نداره و Binding را باید فقط از طریق کد سی شارپ انجام داد؟
تشکر استاد .
 

the_king

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

یا اینکه راهی نداره و Binding را باید فقط از طریق کد سی شارپ انجام داد؟
تشکر استاد .
ابتدای فایل MainWindow.xaml رو ببینید، توصیف المنت از چه نوعی است؟ شما دارید یک المنت Window رو توصیف می کنید، نه یک المنت از کلاس سفارشی فلان که وارث Window ئه. در Window هم طبیعتا اون مشخصه مورد نظر شما نیست که بهش Binding ای معنی داشته باشه.
اول یک کلاس دیگه تعریف کنید، فرضا MyWindow که وارث Window باشه، و اون مشخصه مورد نظرتون رو در اون کلاس MyWindow تعریف کنید.
این کلاس جدید برای اینه که بتونه مستقل از xaml ئه کامپایل بشه، وگرنه نمیشه که در حال توصیف xaml برای MainWindow باشید ولی در عین حال از MainWindow که هنوز توصیف اش کامل نشده المنت ایجاد کنید.
بعد بیایید کد MainWindow.xaml رو ویرایش کنید تا بجای المنت Window یک المنت MyWindow رو توصیف کنه.
و در MainWindow.xaml.cs هم بجای وراثت از Window از MyWindow وراثت باشه.
حالا بعد کامپایل، MainWindow.xaml میتونه از اون مشخصه داخل MyWindow به هر شکلی در توصیف المنت MyWindow استفاده کنه، برای Binding و ...
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
ابتدای فایل MainWindow.xaml رو ببینید، توصیف المنت از چه نوعی است؟ شما دارید یک المنت Window رو توصیف می کنید، نه یک المنت از کلاس سفارشی فلان که وارث Window ئه. در Window هم طبیعتا اون مشخصه مورد نظر شما نیست که بهش Binding ای معنی داشته باشه.

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

اول یک کلاس دیگه تعریف کنید، فرضا MyWindow که وارث Window باشه، و اون مشخصه مورد نظرتون رو در اون کلاس MyWindow تعریف کنید.
این کلاس جدید برای اینه که بتونه مستقل از xaml ئه کامپایل بشه، وگرنه نمیشه که در حال توصیف xaml برای MainWindow باشید ولی در عین حال از MainWindow که هنوز توصیف اش کامل نشده المنت ایجاد کنید.
بعد بیایید کد MainWindow.xaml رو ویرایش کنید تا بجای المنت Window یک المنت MyWindow رو توصیف کنه.
و در MainWindow.xaml.cs هم بجای وراثت از Window از MyWindow وراثت باشه.
حالا بعد کامپایل، MainWindow.xaml میتونه از اون مشخصه داخل MyWindow به هر شکلی در توصیف المنت MyWindow استفاده کنه، برای Binding و ...

آها ، خیلی ممنون .
سئوال اینجاست که چرا همه ی این کارها را روی MainWindow نمیشه انجام داد و دوباره کاری کنیم و مجددا از یه کلاس دیگه ارث بری کنیم؟
یعنی چرا نمیشه در خودِ MainWindow.xaml ، اطلاعاتش را ویرایش کنیم تا بجای المنت Window ، از المنت MainWindow استفاده و توصیف کنه؟


البته جواب تون در اون خط ای که بولد کردم ، هست ؛ اما من دقیق متوجه نشدم .
الان اون خطی که گفتید ، یعنی برای کمپایلِ فایل سی شارپِ و کلاسِ MainWindow.xaml.cs ، به xaml نیاز هست اما برای کمپایلِ اجدادش (و اون نوعی که در تگِ xaml که بصورت پیش فرض ، نوعِ Window هست) ، نیاز به xaml نیست؟
کلا درباره ی اون خط بیشتر توضیح میدین؟
تشکر استاد . :rose:
 

the_king

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



آها ، خیلی ممنون .
سئوال اینجاست که چرا همه ی این کارها را روی MainWindow نمیشه انجام داد و دوباره کاری کنیم و مجددا از یه کلاس دیگه ارث بری کنیم؟
یعنی چرا نمیشه در خودِ MainWindow.xaml ، اطلاعاتش را ویرایش کنیم تا بجای المنت Window ، از المنت MainWindow استفاده و توصیف کنه؟
توضیح دادم براتون. شما فرضا المنت MainWindow رو دارید توصیف می کنید، می خواهید بگید این المنت MainWindow تشکیل شده از یکسری اجزاء، فرضا تشکیل شده از یک کلاس Window ای که داخلش فلان اجزاء هست. Window قبلا توصیف شده، کلاس و توصیف اش کامل ئه و حالا می توانید بگید در توصیف MainWindow از Window استفاده شده. اما وقتی MainWindow رو دارید توصیف می کنید خود MainWindow توصیف کامل شده؟ هنوز توصیف نشده، تازه می خواهید توصیفش کنید. نمی توانید بگید که این MainWindow تشکیل شده از یک MainWindow (که خودش تشکیل شده از یک MainWindow، که تشکیل شده از یک MainWindow...) شما هنوز MainWindow رو توصیف نکرده اید، تازه دارید توصیفش می کنید. توصیف که نمیتونه از چیزی که هنوز توصیفش نکردید برای توصیف خودش استفاده کنه.
البته جواب تون در اون خط ای که بولد کردم ، هست ؛ اما من دقیق متوجه نشدم .
الان اون خطی که گفتید ، یعنی برای کمپایلِ فایل سی شارپِ و کلاسِ MainWindow.xaml.cs ، به xaml نیاز هست اما برای کمپایلِ اجدادش (و اون نوعی که در تگِ xaml که بصورت پیش فرض ، نوعِ Window هست) ، نیاز به xaml نیست؟
کلا درباره ی اون خط بیشتر توضیح میدین؟
ربطی بهم ندارند، مستقل هستند. کامپایل فایل سی شارپ کاری به xaml نداره. اما طبعا در xaml اگر ارجاعی به یک کلاس باشه برای تفسیر اون ارجاع نیاز به کامپایل اون کلاس هست. توضیح بالا رو بخونید، صحبت سر توصیف در xaml ئه، xaml داره چیزی رو توصیف می کنه که هنوز توصیف نشده، نمیشه که توصیف اش وابسته به خودش باشه که داره توصیف میشه. MainWindow چیست؟ MainWindow تشکیل شده از یک MainWindow است که به آن MainWindow می گویند. این چه توصیفی ئه؟
 

SajjadKhati

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

استاد ، گفتم حالا که دارم یه Window به عنوان Base میسازم (اسمش را PoshtibangirToloWindowBase گذاشتم) ، از اونجایی که فعلا 3 تا Window دارم (MainWindow که صفحه ی اصلی نرم افزار هست و SettingWindow که صفحه ی تنظیمات هست و همچنین یه ویندوز دیگه) و در هر 3 تاشون ، این مشترک هست که ویندوز ، شامل DockPanel به عنوان نوار عنوان یا همون Title Bar هست که با کلیک و درگ روی این DockPanel ، پنجره ، جابجا میشه (خودِ DockPanel را داخل Grid ای گذاشتم) .

همچنین این DockPanel ، فقط در بعضی از اون Window ها ، شامل دکمه هایی هست و در بعضی از اون ها ، نیست . (یعنی مثلا در MainWindow ، درونِ این DockPanel ، دکمه های بستن ، minimize ، تنظیمات و ... هست که درونِ SettingWindow نیستن) .

به خودم گفتم که این DockPanel که مشترک هست را پس بهتره به پنجره ی پایه (PoshtibangirToloWindowBase) منتقل کنم که هر بار در هر پنجره ، این رو نسازم .
یعنی مثلا میخواستم شی ای از DockPanel را درون پنجره ی PoshtibangirToloWindowBase بسازم ، اما در پنجره ی فرزندش (یعنی مثلا در پنجره ی MainWindow) ، فقط بیام اعضای داخلِ این DockPanel را مشخص کنم .

------------

برای اجرای این کار :
- اول که برای راحتی کار ، مستقیما اومدم PoshtibangirToloWindowBase را بصورت xaml و بصورت نوعِ Window ایجاد کردم . یعنی از نوع کلاسِ ساده ایجاد نکردم . دیدم که wpf خطا میگیره که کلاس پدر ، نباید از نوعی که حاوی کدهای xaml هست ، باشه .

بعد اومدم PoshtibangirToloWindowBase را بصورت کلاسِ ساده ای که از کلاس Window ارث بری میکنه ، ایجاد کردم .
همه چیز خوب داشت پیش میرفت اما بعد ، چند تا مشکل ایجاد شد .
مثلا نمیشه (یا نمیدونم) که در کلاس فرزند (فرضا در MainWindow) ، چجوری در xaml ، کنترل های مورد نظرم را به عنوانِ Children در این پروپرتیِ DockPanel اضافه کنم .

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

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

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

یا فرضا روش ایجاد user control را پیشنهاد میکنین؟
هر چند user control را امتحان نکردم اما به نظر میرسه که روش ایجادِ user control هم مثل window ئه پدر ، مشکاتی در این باره داشته باشه . چون در کد xaml ، نمیشه بدون اینکه به پروپرتی ای در xaml مقدار بدیم ، بریم و مستقیما به اعضای اون پروپرتی مقدار بدیم .

تشکر استاد .
 

the_king

مدیرکل انجمن
مثلا نمیشه (یا نمیدونم) که در کلاس فرزند (فرضا در MainWindow) ، چجوری در xaml ، کنترل های مورد نظرم را به عنوانِ Children در این پروپرتیِ DockPanel اضافه کنم .
نمیدونم دارید چیکار می کنید ولی اگر نوع یک مشخصه <Collection<T باشه، xaml میتونه بصورت خودکار از InsertItem اش استفاده کنه و بهش عضو اضافه کنه. حالا بعدا می توانید اعضاء مجموعه رو جایی به عنوان فرزند اضافه کنید.
یا اینکه در کلاس فرزند (فرضا در MainWindow) ، در xaml اش نمیشه بدون اینکه به پروپرتی ای در xaml مقدار بدیم ، بریم و مستقیما به اعضای اون پروپرتی مقدار بدیم .
یا اینکه با اضافه کردنِ کنترلی در xaml ، اون اشیایی که در پنجره ی والد در کد سی شارپ (در PoshtibangirToloWindowBase) نوشته بودم ، اتوماتیک زمان اجرا محو میشد .
اگر درست متوجه شده باشم شما یکجا چیزی رو به عنوان Content فلان چیز تعریف می کنید، بعد میایید در جای دیگری برای Content همون مورد توصیف می نویسید، طبیعتا این توصیف تون به معنای جایگزینی مقدار قبلی مشخصه است.
نهایتا اینکه برای این قضیه (زمانی که بعضی از کنترل ها ، در چند ویندوز مشترک هستن و ویندوزِ پدر هم داریم) ، راه حلی دارین یا اینکه همون روش قبلی را پیش برم و همه ی کنترل های مشترک را در هر ویندوزِ فرزند ، جداگانه ایجاد کنم؟
قاعدتا باید مجزا باشند. اگر دنبال این هستید که یک ظاهر در XAML توصیف کنید و بعد با وراثت در XAML های متفاوتی تغییرش بدهید و تکمیلش کنید، همچین چیزی شبیه به وراثت بصری در WPF نیست.
یا فرضا روش ایجاد user control را پیشنهاد میکنین؟
ساختن UserControl برای مواقعی که میخواهید یک جزء با ساختاری رو در جاهای متفاوتی تکثیر کنید مناسبه، ولی ظاهرا شما دنبال وراثت هستید، نه تکثیر.
 

SajjadKhati

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


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

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

ساختن UserControl برای مواقعی که میخواهید یک جزء با ساختاری رو در جاهای متفاوتی تکثیر کنید مناسبه، ولی ظاهرا شما دنبال وراثت هستید، نه تکثیر.

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

تشکر استاد .
 

SajjadKhati

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

تشکر استاد .

پیشنهادتون همونه که با روش قبلی برم؟
یعنی در هر کدوم از ویندوزِ فرزندانش (مثل MainWindow و ...) ، جداگانه اون DockPanel را ایجاد کنم؟
که در این صورت ، کدها تکرار میشن .
یعنی تکرار کدها (روش قبلی) را پیشنهاد میکنین؟
ایجاد user control در اینجا ، کاربرد نداره؟

تشکر استاد .
 

the_king

مدیرکل انجمن
پیشنهادتون همونه که با روش قبلی برم؟
یعنی در هر کدوم از ویندوزِ فرزندانش (مثل MainWindow و ...) ، جداگانه اون DockPanel را ایجاد کنم؟
که در این صورت ، کدها تکرار میشن .
یعنی تکرار کدها (روش قبلی) را پیشنهاد میکنین؟
ایجاد user control در اینجا ، کاربرد نداره؟
من پیشنهادی ندارم. WPF تکنولوژی جدیدی نیست، به قدر کافی آزموده شده. ببینید تا الان چه چیزهایی هست، آنچه که هست به چه شکل طراحی شده اند، شما هم با همون روال ها پیش می روید. طبعا در عرض سیزده چهارده سال هر روش مقبولی که میتونست باشه پیاده شده. بهتره شیوه طراحی خودتون رو باید با آن چیزی که هست مطابقت بدهید، نه اینکه اول یک روش جدیدی در ذهنتون در نظر بگیرید و بعد دنبال راهی برای پیاده سازی اش باشید.
اول باید ببینید در WPF به چه روشی طراحی می کنند، اگر تا به حال پروژه های کمی رو بررسی کرده اید، برای پیدا کردن راهکار ها مرتب به مشکل برخورد می کنید.
 

SajjadKhati

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

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

الان user control به درد جایی میخوره که در چندین Window ، بخوایم ازش استفاده کنیم اما در عین حال ، حداقل در کدهای xaml (در Window ای که از اون user control استفاده میکنیم) ، نخوایم کنترلی به user control مون اضافه کنیم .
اما اگه در کدهای سی شارپ خواستیم به user control ، کنترلی را اضافه کنیم ، مشکلی نیست .

اینهایی که گفتم، درسته؟
تشکر استاد .
 

SajjadKhati

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

الان user control به درد جایی میخوره که در چندین Window ، بخوایم ازش استفاده کنیم اما در عین حال ، حداقل در کدهای xaml (در Window ای که از اون user control استفاده میکنیم) ، نخوایم کنترلی به user control مون اضافه کنیم .
اما اگه در کدهای سی شارپ خواستیم به user control ، کنترلی را اضافه کنیم ، مشکلی نیست .

اینهایی که گفتم، درسته؟
تشکر استاد .

سلامی مجدد
انگار جواب حدودی اش اینه که برای اینکار ، یه کلاسی بسازیم که از UserControl ارث بری کنه و یه DependencyProperty ای براش تعریف کنیم و نکته ی مهم اش اینه که برای این کلاس ، اتریباتسِ ContentProperty را برای اون پروپرتی در نظر بگیریم و بعد هم ازش در Window (در xaml) استفاده کنیم (که البته گفتش راه کاملا مناسبی شاید نباشه) :


و


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

the_king

مدیرکل انجمن
سلامی مجدد
انگار جواب حدودی اش اینه که برای اینکار ، یه کلاسی بسازیم که از UserControl ارث بری کنه و یه DependencyProperty ای براش تعریف کنیم و نکته ی مهم اش اینه که برای این کلاس ، اتریباتسِ ContentProperty را برای اون پروپرتی در نظر بگیریم و بعد هم ازش در Window (در xaml) استفاده کنیم (که البته گفتش راه کاملا مناسبی شاید نباشه) :


و


تشکر استاد .
ContentProperty صرفا نقش خلاصه ساز رو داره، یک مشخصه ای رو تعیین می کنه که بدون نیاز به صریح وارد کردنش بشه بهش مقدار داد.
ContentProperty جزئی از حل یک مساله نیست چون اگر نباشه هم اتفاقی نمی افته.
فرضا ContentProperty این امکان رو میده که بجای اینکه بنویسید :
XML:
        <ContentControl>
            <ContentControl.Content>
                <Grid>
                    <Button/>
                </Grid>
            </ContentControl.Content>
        </ContentControl>
بتوانید خلاصه تر بنویسید :
XML:
        <ContentControl>
                <Grid>
                    <Button/>
                </Grid>
        </ContentControl>
چون در صفات اون کلاس [ContentProperty("Content")] نوشته شده.
یعنی چیزی که به عنوان محتوا بین تگ آغازین و پایانی المنت و بدون مشخص کردن مشخصه خاصی می نویسید رو به عنوان مقدار فلان مشخصه فرض کنه. وگرنه ContentProperty نقش مهمی در اضافه کردن فرزندان و ... نداره.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
ContentProperty صرفا نقش خلاصه ساز رو داره، یک مشخصه ای رو تعیین می کنه که بدون نیاز به صریح وارد کردنش بشه بهش مقدار داد.
ContentProperty جزئی از حل یک مساله نیست چون اگر نباشه هم اتفاقی نمی افته.
فرضا ContentProperty این امکان رو میده که بجای اینکه بنویسید :
XML:
        <ContentControl>
            <ContentControl.Content>
                <Grid>
                    <Button/>
                </Grid>
            </ContentControl.Content>
        </ContentControl>
بتوانید خلاصه تر بنویسید :
XML:
        <ContentControl>
                <Grid>
                    <Button/>
                </Grid>
        </ContentControl>
چون در صفات اون کلاس [ContentProperty("Content")] نوشته شده.
یعنی چیزی که به عنوان محتوا بین تگ آغازین و پایانی المنت و بدون مشخص کردن مشخصه خاصی می نویسید رو به عنوان مقدار فلان مشخصه فرض کنه. وگرنه ContentProperty نقش مهمی در اضافه کردن فرزندان و ... نداره.

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

درباره ی ContentProperty ، بله . درسته . البته خودمم میدونستم ولی یه لحظه یه جور دیگه حساب کرده بودم .

استاد ، میگم این روشش را بررسی کردم (لینک اول در پست 812 را بررسی کردم) . در وهله ی اول (در پروژه ی تمرینی ام) ، جوابِ خوبی داد . تقریبا همون چیزی هه که میخوام (نه خیلی دقیق . مثلا فوکوس ها ، یه کم مورد دارن که طبیعی هست . چون دو سری از کنترل ها در دو جای مختلف (یعنی در ویندوز و در user control) اضافه میشن) .

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

به نظرتون این روش را در پروژه اجرا کنم یا همون روش قبلی که کدها را کپی میکردم؟
شما بودید ، کدوم روش را انتخاب میکردید؟
تشکر استاد .
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
عرض کردم، پیشنهادی ندارم.

حالا استاد یه نظری بدین .

نهایتا هم اگه جوری نشه که من میخواستم ، صرفا یه پنل اضافه میشه و برگردوندنش به حالت اول هم راحته . کار چندانی نداره .
تشکر استاد .
 

SajjadKhati

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

استاد ، در همون لینک ، یعنی در لینک زیر ، جواب اولی :


یعنی در کد زیر :

C#:
    public partial class BaseControl : UserControl
    {
        public UIElementCollection NewControls
        {
            get { return (UIElementCollection)GetValue(NewControlsProperty); }
            set { SetValue(NewControlsProperty, value); }
        }

        public static readonly DependencyProperty NewControlsProperty = DependencyProperty.Register("NewControls", typeof(UIElementCollection), typeof(BaseControl));

        public BaseControl()
        {
            InitializeComponent();

            this.NewControls = new UIElementCollection(this, this);

            this.Loaded += BaseControl_Loaded;
        }

        void BaseControl_Loaded(object sender, RoutedEventArgs e)
        {
            foreach (UIElement element in NewControls.Cast<UIElement>().ToList())
            {
                NewControls.Remove(element);

                this.newPanel.Children.Add(element);
            }
        }
    }


1 - چرا وقتی میخواست حلقه ی foreach بنویسه ، به این صورت نوشت؟ :

C#:
foreach (UIElement element in NewControls.Cast<UIElement>().ToList())

من وقتی نوشتم (البته نام شون متفاوت بود) :

C#:
foreach (UIElement element in NewControls)

ارور زیر را داد :

کد:
System.InvalidOperationException: 'The enumerator is not valid because the collection changed.'

اما حلقه ی foreach ، همونطور که قبلا گفته بودید (و در اسناد مایکروسافت هم گفت) ، مگه با هر نوعی که اینترفیسِ IEnumerable یا IEnumerable<T> را پیاده سازی کنه ، کار نمیکنه؟

اگه این طور باشه ، خوب ، NewControls که از نوع UIElementCollection هست و این کلاس هم که اینترفیسِ IEnumerable را پیاده سازی میکنه (هر چند اینترفیسِ IEnumerable<T> را پیاده سازی نمیکنه) .

اما اگه این ارور و این مشکل ، از پیاده سازی نکردنِ IEnumerable<T> ، توسط UIElementCollection هست ، خوب پس چرا وقتی کد زیر را مینویسیم :

C#:
foreach (UIElement addControl in NewControls.Cast<UIElement>())

باز هم همین ارور را میده؟
اینجا که به شی ای اینترفیسِ IEnumerable<T> تبدیل شده .

پس مشکل از کجاست که حتما باید نوعِ UIElementCollection را به List<T> تبدیل کنیم تا در foreach کار کنه و ارور نده؟


2 - در پارامترهای متد سازنده ی UIElementCollection ، منظور از visualParent ، همون والد در VisualTree هست و همچنین منظور از logicalParent ، والد در LogicalTree هست؟
خوب فرضا هم که اینها را در این کلاس گرفت ؛ چه به دردش میخوره؟
مگه در کالکشن ها که رابطه ی درخت ها را مشخص میکنن؟ وقتی که کنترلی ، به والدی اضافه شد ، اون وقت مشخص نمیکنن؟

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


3 - وقتی کدِ زیر را حذف کنیم :

C#:
NewControls.Remove(element);

و بدون اینکه اول ، متغییرِ element را از کالکشنِ NewControls ، حذف کرده باشیم و مستقیما بخوایم کد زیر را اجرا کنیم :

C#:
this.newPanel.Children.Add(element);

در این صورت ، ارور زیر را میده :

System.InvalidOperationException: 'Specified element is already the logical child of another element. Disconnect it first.'

که میگه المنت تعیین شده (در اینجا ، همون متغییر element) ، قبلا ، فرزند منطقیِ کنترل دیگه ای بوده (کنترل User Control بوده) .
که علت این هم برمیگرده که جواب سئوال دوم ام ، را دقیق متوجه بشم .

تشکر استاد :rose:
 

the_king

مدیرکل انجمن
خیلی ممنون استاد .
تصمیم گرفتم که از طریق ساختن user control انجام بدم .

استاد ، در همون لینک ، یعنی در لینک زیر ، جواب اولی :


یعنی در کد زیر :

C#:
    public partial class BaseControl : UserControl
    {
        public UIElementCollection NewControls
        {
            get { return (UIElementCollection)GetValue(NewControlsProperty); }
            set { SetValue(NewControlsProperty, value); }
        }

        public static readonly DependencyProperty NewControlsProperty = DependencyProperty.Register("NewControls", typeof(UIElementCollection), typeof(BaseControl));

        public BaseControl()
        {
            InitializeComponent();

            this.NewControls = new UIElementCollection(this, this);

            this.Loaded += BaseControl_Loaded;
        }

        void BaseControl_Loaded(object sender, RoutedEventArgs e)
        {
            foreach (UIElement element in NewControls.Cast<UIElement>().ToList())
            {
                NewControls.Remove(element);

                this.newPanel.Children.Add(element);
            }
        }
    }
اول بد نیست یک نگاهی به ساختار IEnumerator بندازید، از ساختارش هم می توانید متوجه بشوید که به چه دلیل اینطوریه، ولی چیزی که میخواهم بهش توجه کنید اینه:
Enumerators can be used to read the data in the collection, but they cannot be used to modify the underlying collection.​
یعنی اگر foreach داره اعضاء فلان مجموعه رو یکی یکی تحویل میده، در این لحظه Enumerator فقط میدونه عضو Current چیه و میتونه از رویش با MoveNext عضو بعدی رو پیدا کنه و اگر این وسط بخواهید تغییری در مجموعه بدهید، حساب و کتاب Enumerator رو می زنید بهم و برای همین نباید مادامی که با اون Enumerator کار می کنید تغییری در مجموعه بدهید.
Enumerator فقط میتونه ریست بشه و از اول دوبار اعضاء رو بشماره.
نتیجه اینکه داخل foreach مجاز به تغییر در مجموعه نیستید، چرا؟ چون Enumerator ئه فقط تا زمانی اعتبار داره که مجموعه دست نخورده است، به محض ویرایش از اعتبار می افته، باید Enumerator جدیدی ایجاد بشه که با منطق foreach جور در نمیاد، نمیشه که foreach قبلی نیمه کاره ول بشه و از آیتم اول foreach دوباره شروع به کار کنه.
1 - چرا وقتی میخواست حلقه ی foreach بنویسه ، به این صورت نوشت؟ :

C#:
foreach (UIElement element in NewControls.Cast<UIElement>().ToList())
چون داخل foreach داره مجموعه NewControls رو ویرایش می کنه، پس در foreach خود NewControls رو نباید پیمایش کنه، میاد ازش یک کپی بصورت List میگیره و اون List رو پیمایش می کنه، نه خود NewControls رو. و طبعا حذف کردن اعضاء NewControls داخل حلقه تاثیری روی اون List نداره. سوالات بعدی تون هم مربوط به همون قضیه است.
2 - در پارامترهای متد سازنده ی UIElementCollection ، منظور از visualParent ، همون والد در VisualTree هست و همچنین منظور از logicalParent ، والد در LogicalTree هست؟
خوب فرضا هم که اینها را در این کلاس گرفت ؛ چه به دردش میخوره؟
به این دردش میخوره که تو کلاس UIElementCollection لازمشون داره و ازشون استفاده می کنه.
فرضا UIElementCollection.Count کد اش اینه :
C#:
public virtual int Count => _visualChildren.Count;
مگه در کالکشن ها که رابطه ی درخت ها را مشخص میکنن؟ وقتی که کنترلی ، به والدی اضافه شد ، اون وقت مشخص نمیکنن؟
Collection ها یعنی کلاس هایی که همه شون یک ویژگی مشترک دارند که اونم پیاده سازی ICollection ئه. فقط همین. یعنی رفتارشون میتونه کاملا متفاوت از هم باشه. ICollection تعیین نمیکنه که کلاس چیکار بکنه یا نکنه.
کلاس x میتونه هر کاری رو انجام بده که طراح اش مشخص کرده و در عین حال ICollection یا IList هم باشه. ICollection که تعیین نمی کنه که کلاس x مجاز به انجام چه کاری هست یا نیست. طراح x ئه که مشخص می کنه چه کار کنه. UIElementCollection کاری رو می کنه که طراحش مشخص کرده، ICollection فقط یک Interface ئه.
کلا چرا لازمه این دو پارامتر ، در این متد سازنده مشخص بشه؟
چون کارکرد شیء کلاس UIElementCollection وابسته به وجود اون دو پارامتر ئه. بدون اونها کارکردی نداره. همانطور که کارکرد کلاس FileInfo وابسته به یک فایل مشخص ئه، اگر اون فایل به عنوان پارامتر ورودی مشخص نشه که کارکرد شیء FileInfo معنی نداره.
و همچنین وقتی میگیم که شیِ UserControl مون ، والد بصری (visualParent) ئه کنترل هایی هست که درون کالشنِ NewControls اضافه میشه ، دقیقا یعنی چه و همچنین باز وقتی میگیم که شیِ UserControl مون ، والد منطقیِ (logicalParent) ئه کنترل هایی هست که درون کالشنِ NewControls اضافه میشه ، دقیقا یعنی چه؟
در WPF دو مفهوم هست که قبلا در موردشون صحبت کرده ایم، Logical Tree و Visual Tree.
فرضا یک Button پیشفرض میتونه در درخت Visual اش یک فرزند ButtonChrome داشته باشه و در درخت Logical اش یک فرزند string با مقدار "OK"
حالا در متد سازنده کلاس مشخص می کنید که این مجموعه در مورد کدوم Logical Tree و Visual Tree ئه، با کدوم Logical Tree و Visual Tree ای کار می کنه. ممکنه Logical Tree اش null باشه یعنی فقط Visual Tree داشته باشه.
و شما در مثال تون میگید این مجموعه با Logical Tree و Visual Tree اون UserControl کار می کنه، یعنی درخت Logical و Visual اون UserControl رو بکار می بره، پس اگر عضوی هم بهش اضافه کنید، در اون دو درخت قرار میگیره.
 

SajjadKhati

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

یعنی اگر foreach داره اعضاء فلان مجموعه رو یکی یکی تحویل میده، در این لحظه Enumerator فقط میدونه عضو Current چیه و میتونه از رویش با MoveNext عضو بعدی رو پیدا کنه و اگر این وسط بخواهید تغییری در مجموعه بدهید، حساب و کتاب Enumerator رو می زنید بهم و برای همین نباید مادامی که با اون Enumerator کار می کنید تغییری در مجموعه بدهید.
Enumerator فقط میتونه ریست بشه و از اول دوبار اعضاء رو بشماره.
نتیجه اینکه داخل foreach مجاز به تغییر در مجموعه نیستید، چرا؟ چون Enumerator ئه فقط تا زمانی اعتبار داره که مجموعه دست نخورده است، به محض ویرایش از اعتبار می افته، باید Enumerator جدیدی ایجاد بشه که با منطق foreach جور در نمیاد، نمیشه که foreach قبلی نیمه کاره ول بشه و از آیتم اول foreach دوباره شروع به کار کنه.

چون داخل foreach داره مجموعه NewControls رو ویرایش می کنه، پس در foreach خود NewControls رو نباید پیمایش کنه، میاد ازش یک کپی بصورت List میگیره و اون List رو پیمایش می کنه، نه خود NewControls رو. و طبعا حذف کردن اعضاء NewControls داخل حلقه تاثیری روی اون List نداره. سوالات بعدی تون هم مربوط به همون قضیه است.

خیلی ممنون استاد .
همینطور بخاطر جواب بقیه ی قسمت ها .


حالا من دقیق متوجه نشدم که چرا کد زیر هم همون ارور را میده؟ :

C#:
foreach (UIElement element in NewControls.Cast<UIElement>())

متد Cast<UIElement>() که شی ای از Enumerable<UIElement> را برمیگردونه . یعنی همون شیِ NewControls (که UIElementCollection) باشه ، نیست .
بنابراین خروجی این متدِ Cast<UIElement>() که شیِ Enumerable<UIElement> هست ، مثلِ متدِ ToList<UIElement>() که شیِ List<UIElement> هست ، شی ای متفاوت از نوع UIElementCollection هست دیگه .
پس چرا فقط زمانی که به List<UIElement> تبدیل میکنیم ، درست کار میکنه؟


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


استاد ، در قضیه ی MVVM :

1 - فرض کنید در همون کلاس Student ، دو تا پروپرتی ای داشتیم از نوع کلاس های A و B .
که همه ی این کلاس ها را درون Model مون تعریف کرده باشیم .
هر کدوم از این کلاسِ A و B ، باز خودشون پروپرتی های بسیار زیادتری داشته باشن از نوع کلاس های دیگه ای که باز درون Model مون تعریف کرده باشیم .

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

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



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

- یک سئوال جنبی اینکه قدمت WinForm واسه سال 2002 هست؟
توی ویکی پدیا ، اولین انتشارش را سال 2002 نوشته (چون اگه اشتباه نکنم ، قبلا بیشتر گفته بودین) .
قدمت wpf هم واسه سال 2006 هست دیگه. درسته؟

تشکر استاد .
 

the_king

مدیرکل انجمن
خیلی ممنون استاد .
همینطور بخاطر جواب بقیه ی قسمت ها .


حالا من دقیق متوجه نشدم که چرا کد زیر هم همون ارور را میده؟ :

C#:
foreach (UIElement element in NewControls.Cast<UIElement>())

متد Cast<UIElement>() که شی ای از Enumerable<UIElement> را برمیگردونه . یعنی همون شیِ NewControls (که UIElementCollection) باشه ، نیست .
این شیء ای که از نوع <Enumerable<UIElement بر میگردونه، چی به foreach تحویل میده؟ طبق توضیحات foreach با GetEnumerator اش یک IEnumerator تحویل میده. حالا این IEnumerator منبع اش کدوم مجموعه است؟ خودش مستقلا یک مجموعه داخلش داره و اعضاء خودش رو بر میگردونه؟ یا <Enumerable<UIElement ئه یک مجموعه کپی از روی NewControls برای خودش ایجاد کرده و اعضاء اون رو بر می گردونه؟ یا مستقیما اعضاء NewControls رو بر میگردونه؟
امتحان کنید و ببینید :
C#:
            var userControl = new UserControl();
            var newControls = new UIElementCollection(userControl, userControl);
            var enumerable = newControls.Cast<UIElement>();
            var enumerator = enumerable.GetEnumerator();
            var fieldInfo = enumerator.GetType().GetField("source", BindingFlags.NonPublic | BindingFlags.Instance);
            var source = fieldInfo?.GetValue(enumerator);
            if (source == newControls)
            {
                MessageBox.Show("yes");
            }

بنابراین خروجی این متدِ Cast<UIElement>() که شیِ Enumerable<UIElement> هست ، مثلِ متدِ ToList<UIElement>() که شیِ List<UIElement> هست ، شی ای متفاوت از نوع UIElementCollection هست دیگه .
اینکه نوع دو شیء متفاوت از هم باشند به رفتار Enumerable هاشون ربطی نداره.
C#:
        public class A : IEnumerable<int>
        {
            public static readonly List<int> Source = new List<int>(new[] { 1, 2, 3 });

            public IEnumerator<int> GetEnumerator()
            {
                return Source.GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }

        public class B : IEnumerable<int>
        {
            public IEnumerator<int> GetEnumerator()
            {
                return A.Source.GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }
C#:
            var b = new B();
            foreach (var i in b)
            {
                A.Source.Sort();
            }

استاد ، در قضیه ی MVVM :

1 - فرض کنید در همون کلاس Student ، دو تا پروپرتی ای داشتیم از نوع کلاس های A و B .
که همه ی این کلاس ها را درون Model مون تعریف کرده باشیم .
هر کدوم از این کلاسِ A و B ، باز خودشون پروپرتی های بسیار زیادتری داشته باشن از نوع کلاس های دیگه ای که باز درون Model مون تعریف کرده باشیم .

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

ثانیا دو گروه کلاس متفاوت هست، شما گروه دوم رو قاطی گروه اول کرده اید. در گروه اول کلاس ها مربوط به استفاده داخلی Model هستند، فرضا در بانک اطلاعاتی هر فردی یک مشخصاتی داره که با یک کد منحصر بفرد (کلید خارجی) در جدول ربط داده میشه به جدول دیگری. کلاسی که حاوی این کلید خارجی است مربوط به بانک اطلاعاتی است، فقط مربوط به Model ئه، قرار نیست به View منتقل بشه. اما در گروه دوم انواع کلاس ها رو دارید که از اساس ساخته شده اند برای تبادل داده، کارشون سادگی تبادل داده بین Model و View ئه، فرضا کلاسی که مشخصات پرسنل رو منتقل می کنه، نه میدونه جدول چیه و نه کاری با کلید جداول داره. این کلاس میتونه داخل ViewModel تعریف بشه، نه در Model، نه در View.
نمیدونم منظورم را متوجه شدین یا نه .
اما اگه این کار را کنیم ، در کلاس ها(ی پیچیده و تو در تویی که در Model تعریف کنیم) ، کار مقداردهی به اعضای یک کلاس Model ، خیلی پیچیده میشه .
این Model طراحی بدی داره.
2 - فرض کنید مقدار پروپرتی ای در Model را از دیتابیس گرفتیم و پُر کردیم .
اگه از View ، عمل Binding ای به این پروپرتی در Model انجام داده باشیم ، که هیچ (که اغلب ، همین طوره) .
اما اگه Binding انجام نداده باشیم و بخوایم توسط ViewModel ، این تغییرات را به View مون اطلاع بدیم ، لازمه که در ViewModel ، یه پروپرتی از نوعِ View مون هم در نظر بگیریم تا توسطش ، به View اطلاع بدیم . درسته؟
نمیدونم سئوال دومی را قبلا پرسیده بودم یا نه .
در مطالب قبلی صحبتش شده. وظیفه هر جزء رو در حد مسئولیت خودش در نظر بگیرید، یعنی فرضا اگر Model قراره موقع تغییر اطلاع بده، مسئولیتش در همین حد ئه که امکانی به ViewModel یا کلا هر موجودیت خارج از خودش بده که اگر خواست، بتونه از تغییر مطلع بشه، فقط همین. اینکه ViewModel به تغییر اهمیت میده یا نمیده، به View خبر میده یا نمیده یا چطور خبر میده به Model ربطی نداره.
همچین Model ای با INotifyPropertyChanged که میگه مقدار فلان مشخصه تغییر کرد، یا یک event خاص خودش میتونه به ViewModel تغییرات رو اطلاع بده. حالا اینکه ViewModel ای که ممکنه شخص دیگری نویسنده اش باشه به این تغییرات اهمیت میده یا نه یا در مقابل این تغییرات با View چه تعاملی بکنه دیگه به Model ربطی نداره.
- یک سئوال جنبی اینکه قدمت WinForm واسه سال 2002 هست؟
اون WinForm ای که میگید اسم پلتفرم داخل NET. ئه، طبعا وقتی خود NET Framework. سال 2002 ارائه شده، WinForm هم نمیتونسته زودتر از 2002 باشه، با خود NET. معرفی شده. اما قدمت Windows Forms که ربطی به NET. نداره، قبل از اینکه NET. ارائه بشه برنامه های تحت Windows Forms و زبان های برنامه نویسی ویژوال با پلتفرم Windows Forms بودند. یعنی از همون موقع که ویندوز این واسط کاربری رو داشته زبان های برنامه نویسی ویژوال اون دوران هم همچین پلتفرمی داشته اند.
توی ویکی پدیا ، اولین انتشارش را سال 2002 نوشته (چون اگه اشتباه نکنم ، قبلا بیشتر گفته بودین).
احتمالا در مورد Windows Forms صحبت می کردم، Windows Forms یک پلتفرم خاص NET. نیست، فرضا در Visual Basic 5 (سال 1997) هم پلتفرم همون Windows Forms ئه.
قدمت wpf هم واسه سال 2006 هست دیگه. درسته؟
بله.
 

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

بالا