משיב מוביל
איך חושפים ComboBox מתוך UserControl

שאלה
-
שלום
אני צריך לממש UC שיהיה הרחבה של ComboBox - הוא יכיל ComboBox ופקד נוסף (textBlock שמציג דבר מה נוסף).
אני מעוניין שלמשתמשי ה-UC תהיה גישה ל-ComboBox עצמו, בכדי שיוכלו לשלוט על מאפיינים כמו DataContext, ItemsSource וכד', וכן על מאפיינים של ה- ComboBoxItems שבתוכו (כמו IsEnabled).
לפי מה שראיתי הדרך הנכונה לעשות זאת היא באמצעות ירושה מ-ComboBox, הבעיה שיש אצלי כבר class אחר שהוא יורש ממנו. האם יש פתרון אחר?
יום שני 11 יוני 2012 12:39
תשובות
-
תשתמש ב AttachedProperty לכל אחד מהפרופרטיז הללו (AttachedProperty זה הבסיס של Behavior, כאן מספיק AttachedProperty).
את ה AP נגדיר ככה -
public class SomeClass { public static object GetMyProperty(DependencyObject obj) { return (object)obj.GetValue(MyPropertyProperty); } public static void SetMyProperty(DependencyObject obj, object value) { obj.SetValue(MyPropertyProperty, value); } // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.RegisterAttached("MyProperty", typeof(object), typeof(SomeClass), new UIPropertyMetadata(null)); }
ואז אפשר יהיה להשתמש בו על כל קונטרול שנרצה:
<ComboBox Margin="31,117,330,167" my:SomeClass.MyProperty="5" />
עכשיו, אתה רוצה שה ComboBox עצמו יוכל להגיב לשינוי של הפרופרטי? (מצטער, אני עדיין לא *לחלוטין* מבין מה אתה מנסה לעשות, אבל אנחנו בדרך הנכונה :) )
- הוצע כתשובה על-ידי Elad R Katz יום רביעי 20 יוני 2012 07:26
- סומן כתשובה על-ידי בים יום רביעי 20 יוני 2012 09:27
יום שלישי 12 יוני 2012 16:18
כל התגובות
-
אני רוצה לחדד שהכוונה היא - שתהיה גישה ל-ComboBox מתוך ה-XAML של המשתמש (כי סתם לחשוף אותו כ-property ולהשתמש בו מתוך הקוד זה לא בעיה)יום שלישי 12 יוני 2012 07:38
-
1. אם זה רק הרחבה, אז כמובן שלרשת מ ComboBox זו הדרך הנכונה - מאיזה מחלקה אתה יורש ולמה?
2. כפתרון שהוא quick and dirty אתה יכול לחשוף את ה ComboBox החוצה ע"י x:FieldModifier =>
<UserControl ...> <Grid> <ComboBox x:FieldModifier="public" x:Name="TheComboBox" /> </Grid> </UserControl>
שים לב שזה פתרון בעייתי היות ואתה שובר encapsulation של הקונטרול שלך (כימוס). יחד עם זאת, יכול להיות שבסיטואציה שאתה מתאר זה הפתרון הכי נוח (כאמור, זה פונקציה של מה שתענה לי על שאלה 1)
יום שלישי 12 יוני 2012 07:42 -
הפתרון הזה לא עוזר לשימוש מתוך ה-XAML, כמו שכתבתי בהוספה לשאלה.
הרעיון הוא שהמשתמש ב-UC יהיה מסוגל לכתוב ב-XAML שלו משהו כזה למשל:
<local:MyComboBox> <TheComboBox ItemsSource = "{Binding}"> </TheComboBox> </local:MyComboBox>
או כזה:
<local:MyComboBox> <TheComboBox> <ComboBoxItem Content="aaa"></ComboBoxItem> <ComboBoxItem Content="bbb"></ComboBoxItem> </TheComboBox> </local:MyComboBox>
הפקד הזה יורש ממחלקת בסיס שעוד פקדים שכתבתי גם יורשים ממנה, והיא בעצמה יורשת מ-UserControl. אני צריך לרשת ממנה כי היא מספקת מתודות ומאפיינים שמשותפים לכל אותם פקדים. כמובן שאם אין ברירה אחרת אני יכול לעבור ל-pattern אחר שבו הוא לא יורש ממנה אלא מכיל אותה כ-member וחושף את כל הפונקציונליות שלה, אבל אני מעדיף שלא כי זה פחות אלגנטי וגם הופך אותו ליוצא דופן ביחס לשאר הפקדים שכן יורשים ממנה (ואולי עוד בעיות שיצוצו בהמשך).
יום שלישי 12 יוני 2012 08:35 -
לא, אתה חייב לרשת מ ComboBox - אתה ניגש לזה מאוד לא נכון.
מחלקת הבסיס שלך היא המקור לטעות כאן, חד וחלק. אתה מוסיף פונקציונאליות ע"י ירושה שזה כמעט תמיד טעות חריפה, כפי שהדוגמא הזו יכולה לחשוף (כאן ספציפית זה רק יוצר לך בעיית ירושה כפולה, אבל הבעיה יותר עמוקה. reuse של קוד *מאוד* בעייתי לעשות על ידי ירושה - ירושה זה בשביל פולימורפיזם כמעט בלבד).
אתה יכול להראות את מחלקת הבסיס שלך? יש כאן שאלה עמוקה יותר שיכולה לפי דעתי לעזור לך מאוד בתכנון הקוד..
- הוצע כתשובה על-ידי Elad R Katz יום רביעי 20 יוני 2012 07:27
יום שלישי 12 יוני 2012 08:51 -
זה משהו כזה בערך:
public abstract class BaseClass : UserControl { public object SomeProperty { get { return (object)GetValue(SomePropertyProperty); } set { SetValue(SomePropertyProperty, value); } } public static readonly DependencyProperty SomePropertyProperty = DependencyProperty.Register( "SomeProperty", typeof(object), typeof(BaseClass), new PropertyMetadata(OnPropertyChangedCallback)); private static void OnPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as BaseClass).OnPropertyChangedCallback(); } protected abstract void OnPropertyChangedCallback(); }
כאשר ה-class-ים היורשים חייבים לממש את OnPropertyChangedCallback. (למעשה יש כמה properties כאלה)
יום שלישי 12 יוני 2012 10:33 -
למה זה טוב?יום שלישי 12 יוני 2012 10:43
-
זו התנהגות כללית שמשותפת לכל הפקדים ואני לא רואה טעם לחזור עליה בכל אחד מהם בנפרד. איך היית עושה את זה אחרת?יום שלישי 12 יוני 2012 11:57
-
-
אני מודה שהידע שלי ב-WPF לא עד כדי כך מעמיק. אפשר איזשהי הכוונה איך לעשות את זה?יום שלישי 12 יוני 2012 13:07
-
אני כרגע כותב מדריך מלא על Behaviors, מא' עד ת' - זו סדרת פוסטים שתגיע ל10 לפי דעתי, כרגע כתבתי רק ארבעה:
1. חלק 0 - מבוא
2. חלק 1- הוספה מבלנד
3. חלק 2- הוספה מויז'ואל סטודיו
4. חלק 3 - יצירת Custom Behaviorזו קריאה קלה וזה יתן לך מספיק חומר רקע. במידה ויהיו שאלות איך לממש את ההתנהגות הזו ע"י Behavior אשמח לענות כאן.
http://blogs.microsoft.co.il/blogs/eladkatz @ElatKt
- נערך על-ידי Elad R Katz יום שלישי 12 יוני 2012 13:14
- הוצע כתשובה על-ידי Elad R Katz יום רביעי 20 יוני 2012 07:27
יום שלישי 12 יוני 2012 13:12 -
תודה. קראתי וזה מעניין. אבל לא ברור לי איך אפשר להשתמש בזה כדי לממש את מה שאני צריך. אני רואה איך אפשר להעזר בזה עבור אספקטים תצוגתיים, אבל איך אני מביא לידי ביטוי את SomeProperty - כ -DP שיש לה Callback - בתוך ה-Behavior ?יום שלישי 12 יוני 2012 14:37
-
עקרונית הסדרה המלאה תענה על כל השאלות, אבל בשביל לקצר זמנים היות וזה לא כתוב, פשוט תגיד לי מה אתה מנסה לעשות עם הפרופרטי הזה, ואראה לך את הכיוון.
מה המטרה של הפרופרטי? פרט נמק וכו'. לא פסדו קוד.
יום שלישי 12 יוני 2012 15:07 -
טוב. הרעיון הוא להרחיב פקד מסוים כך שלצידו יהיה במצבים מסוימים רשום (ב-TextBlock) הערך הקודם שלו.
למעשה יש 3 מאפיינים-
object Commanded - מציין את הערך הקודם של הפקד (comboBox במקרה שלנו), עד לרגע שהמשתמש שומר את הערך.
object Reported - מציין את הערך של השדה שהפקד מייצג בשרת (כלומר יכול להיות מצב שהמשתמש יעדכן ערך אבל בשרת יקח זמן עד שיתעדכן).
Placing Placing - זה enum שמציין את המיקום במסך של ה-TextBlock ביחס לפקד.
חשוב לציין שאני בתור זה שכותב את ה-UC לא מעדכן את ערך המאפיינים האלה באף שלב, רק פועל בהתאם לערך שלהם, וה-user שלי אחראי לעדכן אותם (כאשר כעיקרון הוא אמור להיות מסוגל לעשות עליהם Binding מתוך ה-XAML שלו).
יום שלישי 12 יוני 2012 15:33 -
תשתמש ב AttachedProperty לכל אחד מהפרופרטיז הללו (AttachedProperty זה הבסיס של Behavior, כאן מספיק AttachedProperty).
את ה AP נגדיר ככה -
public class SomeClass { public static object GetMyProperty(DependencyObject obj) { return (object)obj.GetValue(MyPropertyProperty); } public static void SetMyProperty(DependencyObject obj, object value) { obj.SetValue(MyPropertyProperty, value); } // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.RegisterAttached("MyProperty", typeof(object), typeof(SomeClass), new UIPropertyMetadata(null)); }
ואז אפשר יהיה להשתמש בו על כל קונטרול שנרצה:
<ComboBox Margin="31,117,330,167" my:SomeClass.MyProperty="5" />
עכשיו, אתה רוצה שה ComboBox עצמו יוכל להגיב לשינוי של הפרופרטי? (מצטער, אני עדיין לא *לחלוטין* מבין מה אתה מנסה לעשות, אבל אנחנו בדרך הנכונה :) )
- הוצע כתשובה על-ידי Elad R Katz יום רביעי 20 יוני 2012 07:26
- סומן כתשובה על-ידי בים יום רביעי 20 יוני 2012 09:27
יום שלישי 12 יוני 2012 16:18 -
כן. ה- class החדש שאכתוב שיירש מ-ComboBox (שזה אגב משהו שבפני עצמו עוד לא לגמרי ברור לי איך לעשות - פקד חדש שמכיל TextBlock בנוסף לקומבו) , צריך להיות מסוגל לדעת מה ערך הפרופרטי ומתי הוא משתנה.יום רביעי 13 יוני 2012 09:22
-
אוקיי - הצלחתי לגלות איך עושים את זה (בתוך ה-callback של שינוי הפרופרטי אפשר לעשות casting לאובייקט שיורש מ-comboBox, ואז לקרוא למתודה בו).
נשאר לי רק להבין - איך לממש את הירושה? איך יוצרים פקד חדש שמכיל ComboBox + TextBlock באמצעות ירושה מ-ComboBox?
יום חמישי 14 יוני 2012 14:04 -
אחרי הרבה חיפושים הצלחתי למצוא משהו שעובד, אבל זה מורכב ויש עם זה מספר בעיות.
הפתרון - שילבתי בקובץ resources את ה-Default Template של Combobox (שמצאתי ב-MSDN) ושיניתי אותו כך שיתייחס לפקד החדש שלי, ושתלתי בתוכו TextBlock.
והבעיות:
1) זה עובד רק כאשר הפקד נמצא בתוך windows application, אם אני שם אותו ב-class library ומנסה להשתמש בו מבחוץ מתוך Windows application לא רואים את ה-TextBlock, אני משער שזה בגלל שהוא לא מוצא את קובץ ה-resources .
2) כרגע שמתי את ה-TextBlock מעל ל-Combo, אבל הדרישה היא שהמשתמש יוכל ע"י פרופרטי לקבוע את מיקומו ביחס ל-combo (למעלה, למטה, ימין או שמאל) - איך עושים את זה? (בנסיון הקודם שבו כתבתי userControl שלא ירש מ-ComboBox הפתרון היה פשוט, ב-code behind כתבתי פונקציה שהתייחסה לפרופרטי ומיקמה את הפקדים בזמן ריצה).
3) כרגע זה רק ComboBox אבל בהמשך אצטרך לעשות את זה לפקדים נוספים (checkbox, textbox ועוד) - זה אומר שעבור כל אחד מהם אצטרך למצוא את ה-template ולשנות אותו? קצת טרחני.
4) יש דרישה נוספת, שהמשתמש יהיה מסוגל לשנות גם את ה-style של הפקד, והשאלה האם בפתרון הזה זה אפשרי (האם שינוי ה-style לא יגרום לדריסת השינוי שהכנסתי ב-template).
כל זה (מורכבות הפתרון ובעיות הלוואי שהוא יוצר) מעלים את השאלה - האם זו הדרך הנכונה? יש אפשרות יותר פשוטה לרשת מפקד ב-WPF?
יום ראשון 17 יוני 2012 15:00 -
1. במקום לדרוס את ה default template, יש ליצור מחלקה חדשה DerivedComboBox : ComboBox, וליצור default template עבורה שיושב תחת themes/generic.xaml.
הקובץ הזה נטען אוטומאטית.
2. לא ברור לי אם זו הדרך הנכונה האמת, ויש לי הרגשה שזו עדיין שאלה של "להוציא" ממך מה אתה מנסה לעשות.
על פי הספר,א. במידה ואתה רוצה לשנות התנהגות של קונטרול ממש, אז צריך לרשת ממנו מחלקה חדשה.
ב. במידה ואתה רוצה להוסיף לו התנהגות פשוטה, Behavior זו הדרך.
ג. במידה ואתה רוצה לשים קונטרול לידו, UserControl זו הדרך.
הבעיה היא שאתה מתאר מצב של (ג) ו(א) ביחד. אתה רוצה שהקונטרול שלך יתנהג לכל דבר ועניין כמו קומבו (מה שאומר *לא* לעבוד עם userControl), אבל אתה רוצה לשנות את מה שיושב לידו, ועדיין שזה יהיה סגור במחלקה אחת... :-/
אולי תתן דוגמאות עוד יותר מפורשות ממה שנתת עד עכשיו? ממש תמונות מסך של שימושים שונים שאתה רוצה לעשות בקונטרול?
יום שני 18 יוני 2012 09:10 -
אני מצטער שלא הייתי מובן, כנראה שבכתב יותר קשה להסביר מאשר בעל פה... מסתבר שאני אכן צריך שילוב של א' עם ג', כי מצד אחד זה אמור להיות מורכב מ-2 פקדים, ומצד שני המשתמש אמור להרגיש שזה combobox לכל דבר, עם תוספת קטנה.
בכל אופן הסתדרתי בסוף. אני לוקח את הפתרון הזה למרות הבעיות שתיארתי קודם. בעיה (1) נפתרה תודות לתגובה שלך, בעיה (2) נפתרה ע"י שימוש בטריגרים (יש דוגמה למשהו דומה ב-template של סליידר), לגבי (4) הסתבר שזה אפשרי, ולגבי (3) אין ברירה, אני אצטרך לעשות את זה לכל פקד בנפרד, אבל זה מחיר שאני מוכן לשלם כי כנראה שזה הפתרון היחידי לבעיה.
בכל מקרה תודה רבה על כל העזרה! אמנם חלק מצאתי לבד אבל לא הייתי מגיע לזה אלמלא ההכוונה... ממש תודה.
יום שלישי 19 יוני 2012 09:03 -
בשמחה (ולמצוא לבד בסופו של דבר זה הכי טוב :)
אני עדיין חושב ששימוש יצירתי ב Behaviors (או Trigger-Action) היה יכול לפתור לך את זה בצורה יותר טובה, אבל אני עדיין לא ממש סגור על מה שניסית לעשות כך שאני לא באמת יודע :))
אנא סמן את התשובה שהכי עזרה לך כתשובה, על מנת שהשאלה תיחשב כ"סגורה".
יום רביעי 20 יוני 2012 07:26 -
יתכן, אבל בהתבסס על הידע שיש לי עכשיו (שהוא רב יותר יחסית לתחילת השרשור...) מסתבר שמה שרציתי הוא באמת לא טריוואלי, כמו שאמרת, לכן אני לא יודע אם יש פתרון פחות "עקום".
מה שבטוח, זה היה מאוד מעניין.
יום רביעי 20 יוני 2012 09:31