PDA

View Full Version : سوال: Binding کردن یک پراپرتی



birtemp
جمعه 16 فروردین 1392, 12:18 عصر
سلام
من دارم یه کنترل WpfControlLibrary طراحی می کنم و واسه این کنترل چند تا پراپرتی به نام های title, value,thickness تعریف کردم. من تو داخل فایل xaml این کنترل هم دو تا کنترل یکیش arc از نوع shape و یه textblock تعریف کردم.
حالا می خوام مشخصه text کنترل textblock رو به پراپرتی value کنترل متصل (binding) کنم. من کار اتصال کردن رو انجام می دم ولی وقتی از کنترل تو برنامه ای استفاده می کنم و value کنترل رو مقداردهی می کنم ، هیچ مقداری داخل textblock قرار نمی گیره.
هیچ خطایی هم نمیده تا ببینم دردش از کجاست :(

لطفاً در این مورد کمکی به من بکنین ;)
پروژه رو براتون اتچ کردم
پیشاپیش تشکر

لینک پروژه:
http://bayanbox.ir/blog/ui-designer/WpfControlLibrary1.rar?info (http://bayanbox.ir/blog/ui-designer/WpfControlLibrary1.rar?info)

رافعی مهدی
سه شنبه 27 فروردین 1392, 16:43 عصر
سلام
این کد، یه خــــــورده درد میکشه :چشمک: بیا با هم دردهاش رو بررسی کنیم!

چند نکته:

نکته 1:


<Border x:Name="border" Height="{Binding Width, ElementName=border}" CornerRadius="20000">
رو بهتره به این شکل بنویسیم:


<Border x:Name="border" Height="{Binding Width, RelativeSource={RelativeSource Mode=Self}}" CornerRadius="20000">

نکته 2:
در اینجا شما چندتا DP تعریف کردید، Title، Value و Thickness و میخواهید در المان TextBlock پراپرتی Text رو به یکی از آنها (Value) بایند کنید. المان TextBlock باید در جایی دنبال پراپرتی Value بگردد، اما کجا؟ وقتی میخواهیم یک DP) DependencyProperty) رو به یک المان در XAML متصل کنیم، باید DataContext اون المان به Object ای وصل باشه که DP مورد نظر رو در بر گرفته. بنابراین در اینجا، باید خود کلاس UserControl1 رو به عنوان Object دربرگیرنده Value معرفی کنیم. نکته ای که در اینجا وجود داره اینه که المانها میتونند از DataContext المان والد خودشون استفاده کنند، در نتیجه، متداول هست که DataContext رو برای المان ریشه (Root Element) در کنترل تعریف میکنند تا همه المانهای داخلی از همون DataContext استفاده کنند، مگر اینکه در یک UserControl بخواهید از چندتا DataContext مختلف استفاده کنید (که البته کار معمول و متعارفی نیست.) بنابراین من المان ریشه رو به این شکل تغییر دادم:


<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UserControl1}}}">

که در اون local، فضای نام UserControl1 هست و به این صورت در قسمت معرفی فضاهای نام (در ابتدای فایل UserControl1.xaml) تعریف میشه:


xmlns:local="clr-namespace:WpfControlLibrary1"

حالا برای بایند کردن Text به Value کافیه بنویسیم:


Text="{Binding Value}"

البته این تنها روش ممکن نیست، میتونستید از پراپرتی Source در Binding هم استفاده کنید که چون در اینجا نیازی به روش دیگری ندارید، از تشریح این روش صرفنظر میکنم.

نتیجه اعمال نکات 1 و 2:

<UserControl x:Class="WpfControlLibrary1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
xmlns:local="clr-namespace:WpfControlLibrary1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" x:Name="userControl">

<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UserControl1}}}">
<Border x:Name="border" Height="{Binding Width, RelativeSource={RelativeSource Mode=Self}}" CornerRadius="20000">
<TextBlock HorizontalAlignment="Left" Height="50" TextWrapping="Wrap" Text="{Binding Value}" VerticalAlignment="Top" Width="100" FontSize="16" Background="#FFF3F3F3" Margin="104,88,0,0"/>
</Border>
<ed:Arc x:Name="arc" Stretch="None" FlowDirection="RightToLeft" Height="{Binding Height, ElementName=border}" Width="{Binding Width, ElementName=border}" Stroke="#FFBB4949" StrokeThickness="19"/>
</Grid>
</UserControl>


نکته 3:
جنس DP تعریف شده (در اینجا Value) با جنس پراپرتی مورد نظر برای بایند (در اینجا Text) باید همسان باشند، در غیر اینصورت باید از یک Converter برای تبدیل Type استفاده کنید. در این پروژه شما Value رو از نوع int تعریف کردید، در حالی که Text از جنس string هست. پس باید نوع Value رو به string تغییر بدید.

نکته 4:
برای رجیستر کردن یک DP، نمیتونید از هر عبارت دلخواهی به عنوان name استفاده کنید. بنابراین عبارت


public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Main Content", typeof(int), typeof(UserControl1));

رو به

public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(UserControl1));

تغییر دادم. این موضوع رو برای Thickness و Title خودتان درست کنید. ("Progress bar Thickness" معتبر نیست!)

نکته 5:
اگر همچنان بر تعریف Value با نوع داده int اصرار دارید، همونطور که در بالا گفتم، لازمه از یک مبدل استفاده کنید. در اینجا تعریف یک Converter رو میبینیم. این کلاس رو به برنامه اضافه کنید:


using System;
using System.Windows.Data;

namespace WpfControlLibrary1
{
public class StringToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return int.Parse(value.ToString());
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString();
}
}
}

در اینصورت داریم:


<UserControl x:Class="WpfControlLibrary1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
xmlns:local="clr-namespace:WpfControlLibrary1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" x:Name="userControl">

<UserControl.Resources>
<local:StringToIntConverter x:Key="stringToIntConverter"/>
</UserControl.Resources>

<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UserControl1}}}">
<Border x:Name="border" Height="{Binding Width, RelativeSource={RelativeSource Mode=Self}}" CornerRadius="20000">
<TextBlock HorizontalAlignment="Left" Height="50" TextWrapping="Wrap" Text="{Binding Value, Converter={StaticResource stringToIntConverter}}" VerticalAlignment="Top" Width="100" FontSize="16" Background="#FFF3F3F3" Margin="104,88,0,0"/>
</Border>
<ed:Arc x:Name="arc" Stretch="None" FlowDirection="RightToLeft" Height="{Binding Height, ElementName=border}" Width="{Binding Width, ElementName=border}" Stroke="#FFBB4949" StrokeThickness="19"/>
</Grid>
</UserControl>


نکته 6:
اگر پشیمان شدید :چشمک: و نمیخواهید از Converter استفاده کنید (یعنی همون تغییر Value به string رو ترجیح میدید) و مایلید در متن TextBlock فقط مقادیر int قرار بگیرند، میتونید قبل از پاس داده شدن مقدار، از فرآیند «اعتبار سنجی» (Validation) استفاده کنید. برای این کار داریم:



#region ________________________________________ Value

/// <summary>
/// [Wrapper property for ValueProperty]
/// <para></para>
/// </summary>
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}

public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value",
typeof(string),
typeof(UserControl1),
new FrameworkPropertyMetadata("0", FrameworkPropertyMetadataOptions.None),
OnValidateValue);

private static bool OnValidateValue(object value)
{
int iValue;

return int.TryParse(value.ToString(), out iValue);
}

اینطوری اگر هر مقداری غیر از یک عدد صحیح به Value نسبت داده بشه، کامپایلر خطا میگیره. (.'blabla' is not a valid value for property 'Value')...

نکته 7:
در Solution ای که گذاشتید، پروژه Startup از نوع ClassLibrary هست که مانع از اجرای برنامه میشه، روی WpfApplication1 راست کلیک کنید و گزینه Set as Startup Project رو انتخاب کنید. در ضمن پروژه WpfApplication2 رو اگر نیازی ندارید، پاک کنید.

نکته آخر:
بیشتر مطالعه کنید! درگیر شدن با WPF بدون مطالعه زیاد و کسب اطلاعات کافی، به سرعت خسته کننده و ملال آور میشه، WPF یک تکنولوژی نسبتاً پیچیده است، فقط با کسب اطلاعات کافی هست که میتونید از کد نویسی با این تکنولوژی لذت ببرید.

موفق باشید.