PDA

View Full Version : حرفه ای: چرا DataTrigger موجود در Interactions در زمان-طراحی تأثیر خودش رو نشون نمیده؟



رافعی مهدی
یک شنبه 25 فروردین 1392, 21:49 عصر
سلام

اول اجازه بدید یه کم قصه تعریف کنم!

در برنامه ام یه UserControl دارم که نیاز به یک Storyboard با key-frame بایند شده به یک DP از همین UserControl داره. (پیچیده که نگفتم :چشمک: ) همه دوستان میدونند که بایند کردن یه Storyboard به یه DP یه نکته کوچیـــــک داره! storyboard ها قابل یخ زدن (Freezable) هستند (عجب ترجمه ای شد!) و به همین دلیل نمیشه باهاشون کارهایی از این دست رو به راحتی انجام داد. من قبلاً در اینجا (http://stackoverflow.com/q/15848381/668342) روش انجام این کار رو به صورت کامل توضیح دادم. (ببخشید که لینک انگلیسی میدم، ولی واقعاً دوباره به فارسی نوشتنش خیلی طول میکشه.)
خلاصه چیزهایی که اونجا نوشتم اینه که اگر مستقیماً در ControlTemplate.Resources یه Storyboard رو قرار بدیم، دیگه نمیشه (یا من بلد نیستم) key-frame ش رو به یه DP بایند کرد، Trick ای که در اینجا وجود داره اینه که storyboard رو ببریم در یکی از المانهای داخل Template. این کار یه مشکل رو (بایند نشدن key-frame) حل میکنه، یه مشکل جدید به وجود میاره! در این صورت دیگه به انواع Triggerها به جز EventTrigger دسترسی نخواهیم داشت. من در این UserControl به یه DataTrigger نیاز دارم تا بتونم زمانی که یکی از DPها تغییر مقدار میده و مساوی با یک مقدار مشخص میشه، یک Storyboard با keyframe بایند شده به یک DP (دیگه) رو شروع کنم.

خب آیا تمام انواع Trigger همین چهار تا هستند؟
1- Trigger
2- MultiTrigger
3- DataTrigger
4- MultiDataTrigger

البته که نه، تعداد بسیار زیادی Trigger دیگه هم وجود داره، که هر کدوم در جایگاه خودشان کلی کاربرد دارند! با اضافه کردن reference دو تا dll به برنامه، که خوشبختانه مربوط به خود Microsoft هم هستند و از بابت صحت و سقم کدهای داخلی اونها هیچ نگرانی وجود نداره، میتونید چند تا از این تعداد زیاد Trigger رو ببینید. بقیه این تعداد زیاد رو هم خودتان میتونید بسازید. :چشمک:

یه نمونه کد از یک Trigger جدید رو در همون تاپیکی که لینکش رو دادم، توضیح دادم.

خب حالا بریم سر اصل مطلب! میخواهم برای UserControl ای که توضیح دادم، با استفاده از DataTrigger موجود در یکی از اون دو تا dll که یادم رفت معرفی‏شون کنم، storyboard مورد نظر رو play کنم.

این دو تا dll:


System.Windows.Interactivity
Microsoft.Expression.Interactions


این هم کد:

<UserControl x:Class="WpfApplication1.UserControl2"
...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:local="clr-namespace:WpfApplication1">

<UserControl.Resources>
<Style x:Key="MyControlStyle" TargetType="UserControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.Resources>
<Storyboard x:Key="MyStory">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brdBase">
<SplineColorKeyFrame KeyTime="0:0:1" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UserControl2}}, Path=SpecialColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>

<Border x:Name="brdBase" BorderThickness="1" BorderBrush="Gray">
<TextBox Text="{Binding SpecialText}"/>
</Border>

<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding SpecialText}" Value="Fire!">
<ei:ControlStoryboardAction Storyboard="{StaticResource MyStory}" ControlStoryboardOption="Play"/>
</ei:DataTrigger>
</i:Interaction.Triggers>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>

<Grid x:Name="grdRoot" DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UserControl2}}}">
<UserControl Style="{DynamicResource MyControlStyle}"/>
</Grid>

</UserControl>

کد #C:


public partial class UserControl2 : UserControl
{
#region ________________________________________ SpecialColor

public Color SpecialColor
{
get { return (Color)GetValue(SpecialColorProperty); }
set { SetValue(SpecialColorProperty, value); }
}

public static readonly DependencyProperty SpecialColorProperty =
DependencyProperty.Register("SpecialColor",
typeof(Color),
typeof(UserControl2),
new FrameworkPropertyMetadata(Colors.Red));
#endregion


#region ________________________________________ SpecialText

public string SpecialText
{
get { return (string)GetValue(SpecialTextProperty); }
set { SetValue(SpecialTextProperty, value); }
}

public static readonly DependencyProperty SpecialTextProperty =
DependencyProperty.Register("SpecialText",
typeof(string),
typeof(UserControl2),
new FrameworkPropertyMetadata(string.Empty));

#endregion


public UserControl2()
{
InitializeComponent();
}
}

انتظار دارم در این کد، وقتی مقدار SpecialText رو برابر با !Fire ست میکنم، MyStory شروع بشه. این اتفاق با تایپ کردن عبارت !Fire در تکست باکس این UserControl در هنگام-اجرای برنامه میفته، ولی وقتی مقدار این DP رو در xaml تعیین میکنم، به صورت زیر، این اتفاق در زمان-طراحی نمیفته و فقط بعد از اجرای کد مثل روش قبل (تایپ کردن) اثرش رو نشون میده.


<Grid>
<local:UserControl2 SpecialText="Fire!"/>
</Grid>

کسی از دوستان عزیز میدونه چطور میشه این مشکل رو بر طرف کرد؟

_________________
پاورقی: من این سوال رو دارم به صورت موازی در StackOverflow (http://stackoverflow.com/q/15995870/668342) و msdn (http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/3d1b27bd-89c0-4a5f-a247-7e703f28e0ab) هم دنبال میکنم. اگر دوست داشتید خوشحال میشم در اون فرومها هم نظر بدید. خیلی ممنون!

رافعی مهدی
دوشنبه 26 فروردین 1392, 07:54 صبح
خب، آقای Javaman II، در msdn تا اینجا یه جواب بحث برانگیز دادند، ایشون معتقدند که در یک UserControl (حتی در MVVM) میشه در مواردی مشابه با این سوال، از code behind استفاده کرد و storyboard مورد نظر رو از طریق code فراخوانی کرد. سوالی که در اینجا مطرحه اینه که واقعاً پافشاری برای نوشتن برنامه ای که هیچ استفاده ای از code behind برای انجام کارهای گرافیکی-اینترفیسی نمیکنه تا چه حد درسته؟ به عبارت دیگه چه لزومی داره که سعی کنیم همه کارهای مربوط به GUI رو فقط از طریق XAML انجام بدیم؟ آیا استفاده از code behind در این امور صرفاً برای پوشش دادن نقاط ضعف XAML یا نقاط ضعف ما در استفاده از XAML هست؟ کاملاً واضحه که این کد رو بدون کوچکترین دردسری میشد از اول با استفاده از code behind نوشت. نظر شما چیه؟

رافعی مهدی
دوشنبه 26 فروردین 1392, 09:52 صبح
Mr. Javaman II پاسخ دقیقتری داد و من که متقاعد شدم:


Bottom line in MVVM and code behind in the view is this:
Anything associated directly with the view such as an animation or other similar view related things are perfectly fine living in the code-behind.

sia_2007
دوشنبه 26 فروردین 1392, 22:08 عصر
استفاده از کد #C برای مسائل مربوط به UI در روش به اصطلاح پشت فرم کد زدن ابدا مشکلی ندارد، در معماری MVVM شما مسائل نا مربوط به UI مانند منطق رو به View Model منتقل می کنید

البته بهتر است کدهای #C در قالب Behavior ها و سایر روش های استاندارد ( مانند ارث بری و ... ) مدیریت شوند

البته مخصوصا در کدنویسی خیلی سطح پایین، که شما وارد درست کردن Data Grid و یا توسعه Data Grid های موجود می شوید، اجبار به استفاده از کد #C بیشتر نیز می شود.

رافعی مهدی
سه شنبه 27 فروردین 1392, 07:52 صبح
ممنونم sia_2007

افراد بسیاری در دنیا هستند که به عنوان طرفداران متعصب پترن MVVM با نوشتن هر کدی در پشت فرم مخالفند و تعداد اونها اینقدر زیاده که گاهی آدم رو به شک می اندازه که واقعاً کدوم روش درسته؟ البته افراد میانه رو مثل شما و Javaman II و ... همین دیدگاه رو دارند که استفاده از code-behind در مسائل صرفاً مربوط به GUI اشکالی نداره. طرفداران پترن MVVM هر روز بیشتر و بیشتر شدند، مخصوصاً هنگامی که اعلام شد Microsoft نرم افزار Expression Blend رو به طور کامل بر اساس پترن MVVM طراحی و تولید کرده. عده زیادی از برنامه نویسان با خوندن مقاله معروف (http://msdn.microsoft.com/en-us/magazine/dd419663.aspx) Josh Smith در msdn، به این باور رسیدند که استفاده از code-behind در این پترن به هیچ وجه الزامی نداره. هر چند که پس از مدتی Smith در پاسخ به یکی از پست های فروم msdn (http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/5b835e0a-457b-4bb3-8c18-4d8dfdcf8fd3/) رسماً اعلام کرد که این برداشت نسبتاً همگانی از عدم وجود code-behind درست نیست و عذرخواهی کرد اگر مقاله اش رو جوری نوشته که این برداشت ازش میشده! لطفاً اینجا (http://barnamenevis.org/showthread.php?391274-UserControlهای-تو-در-تو-در-MVVM&p=1735212&viewfull=1#post1735212) رو ببینید.

کلاً موضوع UserControl در پترن MVVM تا حدی بحث برانگیزه. چند وقت پیش که داشتم با یکی از دوستانم در این خصوص صحبت میکردم میگفت:

I'd say that certain situations like the UserControl aren't what the MVVM proposed to be.
برنامه نویسانی از این دست، که به نظر من بهترین دیدگاه رو دارند، بر این باورند که Custom Control اون چیزی هست که در MVVM معنای عمیقتری نسبت به User Control داره. Javaman II هم همین دیدگاه رو مطرح کرده، اونجا که میگه برای ایجاد کنترلهای سفارشی دو تا روش وجود داره:

one is using a known XAML type such as a user control with DPs in it, and the other is to create a custom control from scratch relying on DefaultKeyStyleProperty registration.
و در ادامه میگه که روش دوم، یعنی استفاده از Custom Controlها بهترین راه در MVVM هست ولی در عین حال ساده ترین راه نیست. درست به همین دلیل هم هست که میشه در MVVM کنترلهای سفارشی حتی بسیار پیچیده ای درست کرد که در زمان استفاده از اونها در XAML دیگه به هیچ کد پشت زمینه ای نیاز نباشه. گذشته از این، اون دسته از طرفداران MVVM که به استفاده از ViewModel حتی برای پراپرتی های UserControl اعتقاد دارند، (که به عقیده من اعتقاد کاملاً نادرستی هست.) راههای مختلفی رو برای تعامل با این Controlها مخصوصاً در شرایطی که به صورت آشیانه ای (Nested) از اونها استفاده میشه پیشنهاد میدهند. مثلاً نگاهی به پاسخ akjoshi در اینجا (http://stackoverflow.com/a/3334780) یا پاسخ Jimmy در اینجا (http://stackoverflow.com/a/9292860) بیندازید. عده دیگری هم مثل Geert Van Horrik و طرفدارانش هستند که برای مواجهه با مشکلات به کارگیری UserControlها در MVVM راه حل دیگری مثل استفاده از Frameworkهای ثانویه رو ارائه میدهند. (برای اطلاعات بیشتر به قسمت سوم (http://www.codeproject.com/Articles/129920/Catel-Part-3-of-n-The-MVVM-Framework) از مقاله چند مرحله ای تشریح فریم ورک Catel مراجعه کنید.)

دیگه خیلی داره طولانی میشه، خلاصه اینکه من به شخصه معتقدم تا اونجا که امکان داشته باشه بهتره از code-behind استفاده نشه، مگر اینکه ضرورت داشته باشه و البته گاهی برای اجتناب از code-behind روش های متعددی در XAML تست و بررسی میشوند، مثل همین سوالی که در این تاپیک مطرح کردم و چون راه حلی برای پیاده سازی بدون استفاده از code-behind براش پیدا نمیشه، میریم سراغ استفاده ترکیبی از XAML و code-behind به عنوان بخشی از V در MVVM. (هنوز هم ممنون میشم اگر کسی راه حلی برای استفاده از storyboardهای بایند شده به یک DP رو با استفاده از DataTrigger ارائه کنه.)

sia_2007
چهارشنبه 28 فروردین 1392, 23:32 عصر
نکته ی خیلی مهمتری که وجود داره زمانی پیش می آد که شما می خواهید در پلتفرم های ضعیف تری مثل Silverlight و یا HTML از MVVM استفاده کنید، اونجا میزان استفاده از #C و Java Script بیشتر هم می شه، پس باید باهاش راه اومد