ستونهای Dynamic در کنترل DataGrid درWPF

در این مقاله متدی را برای ارتباط چند به چند جداول در WPF توضیح خواهیم داد که می توانند در کنترل DataGrid نمایش داده شوند و اصلاح شوند. ردیف ها و ستون ها می توانند اضافه ، حذف یا اصلاح شوند.

ستونهای Dynamic در کنترل  DataGrid  درWPF

آکادمی برنامه نویسان ، برگزار کننده دوره های آموزش برنامه نویسی با استفاده از اساتید مجرب و حرفه ای در سراسر ایران .

[ جهت مشاهده دوره های درحال ثبت نام کلیک کنید ]

ارائه مدارک معتبر آموزشی و ورود به بازار کار .

کنترل  DataGrid  ابزار خوبی برای نمایش داده های ذخیره شده در یک جدول است.  یک ردیف در جدول دیتابیس برابر با یک ردیف در  DataGrid است . ممکن است داده ها در جدول های چندگانه ذخیره شوند ، (جدول  a و جدول  b) و جدول ها رابطه یک به چند داشته باشند. این نوع از داده ها می توانند در یک master-detail نشان داده شوند. نوع دیگری از ارتباطات می تواند ارتباط چند به چند باشد.

در این مقاله متدی را برای ارتباط چند به چند جداول در WPF توضیح خواهیم داد که می توانند در کنترل DataGrid  نمایش داده شوند و اصلاح شوند. ردیف ها و ستون ها می توانند اضافه ، حذف یا اصلاح شوند.

در این قسمت بر روی راه حلی برای ستونهای پویا یا Dynamic تمرکز داریم . به منظور ساده سازی آن محدودیت های معماری را شکسته ایم ، و آن این است که اشیاء لایه های بالایی نمی توانند در لایه های پایین تر استفاده شوند.

مثال زیر پیاده سازی یک فرم مدیریت کاربران است که در آن کاربران ، نقش ها و انتساب ها می توانند اداره شوند. نقش ها و کاربران در دو DataGrid نمایش داده می شوند. انتساب کاربران در datagrid  کاربران تخصیص داده می شود. اگرچه این grid  محتوای  Dynamic دارد، نمایش هر نقش به صورت ستون  chechbox جداگانه ای است. انتساب نقش کاربران توسط چک کردن checkbox مربوطه انجام میشود.

مدل داده ای این مثال شامل یک جدول User ، یک جدول  Role و یک جدول   UserRoleکه جدول همبستگی بین دو جدول دیگر است می باشد (یک کاربر بتواند چند نقش داشته باشد).

مدل داده ای با استفاده از  .NET DataSet پیاده سازی می شود که یک پایگاه داده درون حافظه ای یکپارچه است، و شامل اشاره گرهای اطلاع رسانی  built-in میشود که درج ، حذف و اصلاح ردیف های داده را منتشر میکند. محتوا را می توان به یک فایل XML ذخیره کرد که به عنوان یک مکانیزم در این مثال استفاده می شود.

 

اجزاء و نمودار کلاس

نمودار اجزاء ، لایه بندی نرم افزار را نمایش می دهد:

 Application    .  شامل عناصر  GUI

ViewModel     .  شامل منطق کاری

DataModel     .   شامل تعریف داده ها و تداوم آنها

 

Application

MainWindow    .   : تعاریف GUI   در XAML نوشته می شوند

DataGridColumnsBehavior     .   : یک رفتار متصل که اجازه اصلاح ستونهای متصل کنترل DataGrid  را می دهد.

UserRoleValueConverter     .  : مقدار مبدل است که تعریف میکند زمانی که کاربر در checkbox تیک میخورد یا نه  چه اتفاقی افتاده است .

 

ViewModel

MainViewModel     .   : شامل خواص جدول داده ها برای نمایش و منطق داده ای برای اداره ستون پویا

ColumnTag     .   : ویژگی های پیوست شده برای برچسب زدن اشیاء به موارد مشتق شده از DependencyObject ، در این نمونه DataGridColumn

 

DataModel

DatabaseContext     .   : نمونه singleton که شامل UserRoleDataSet است

UserRoleDataSet     .   : پیاده سازی پایگاه داده بر اساس DataSet

 

اتصال داده ها

این برنامه را با استفاده از الگوی  MVVMمی نویسیم .

   1. جدول Role به کنترل Role DataGrid متصل می شود.

   2. جدول  User به کنترل  User DataGrid متصل می شود.

   3. مجموعه ستونهای قابل مشاهده به ویژگی ستونهای کنترل  User Grid متصل می شود. رفتار ستون پویا با استفاده از این ویزگی به دست می آید، زیرا منطق در  ViewModel  ستونها را از این مجموعه کم یا به آن اضافه میکند.

خاصیت ستونهای کنترل  data grid به صورت  read-only تعریف می شود، پس نمی تواند به یک ویژگی ViewModel  محدود شود. DataGridColumnsBehavior یک رفتار متصل شده است که این محدودیت را پوشش می دهد.

<Window x:Class="Application.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:attachedBehaviors="clr-namespace:Application.AttachedBehaviors"
        xmlns:viewModel="clr-namespace:ViewModel;assembly=ViewModel"
        Title="User Administration" Height="350" Width="525">

    <Window.DataContext>
        <viewModel:MainViewModel/>
    </Window.DataContext>

    <DockPanel LastChildFill="True">
        <ToolBar DockPanel.Dock="Top">
            <Button Content="Save" Command="{Binding SaveCommand}"/>
        </ToolBar>

        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="146*"/>
                <RowDefinition Height="147*"/>
            </Grid.RowDefinitions>

            <GroupBox x:Name="UsersGroupBox"
                      Grid.Column="0"
                      Header="انتساب نقش کاربر">
                <DataGrid x:Name="DataGridUsers"
                          ItemsSource="{Binding Users}"
                          attachedBehaviors:DataGridColumnsBehavior.BindableColumns=
                              "{Binding UserRoleColumns}"
                          AutoGenerateColumns="False"
                          EnableRowVirtualization="False"/>
            </GroupBox>

            <GroupBox x:Name="RolesGroupBox"
                      Grid.Row="1" Grid.Column="0"
                      Header="نقش ها ">
                <DataGrid x:Name="DataGridRoles"
                          ItemsSource="{Binding Roles}"
                          AutoGenerateColumns="False">
                    <DataGrid.Columns>
                        <DataGridTextColumn Header="نام"
                                            Binding="{Binding Name}"/>
                    </DataGrid.Columns>
                </DataGrid>
            </GroupBox>
        </Grid>
    </DockPanel>
</Window>

داده ها در سه جدول در UserRoleDataSet نگهداری می شوند. جدول های role   و  User توسط  DataView  به کنترل  data grid   محدود می شود. فیلترینگ و مرتب سازی می توانند در DataView  تنظیم شوند. کنترل DataGrid  می تواند دستکاری داده ها را با استفاده از  DataView اداره کند. ردیف ها در کنترل  data grid می توانند  درج ، اصلاح و یا حذف شوند و جدول داده ها مستقیما از طریق  DataView  بروزرسانی شوند.

public class MainViewModel
{
    public MainViewModel()
    {
        --- Code omitted ---
        this.UserRoleColumns = new ObservableCollection<DataGridColumn>();
        --- Code omitted ---
    }

    public DataView Users
    {
        get
        {
            return this.dataContext.DataSet.User.DefaultView;
        }
    }

    public DataView Roles
    {
        get
        {
            return this.dataContext.DataSet.Role.DefaultView;
        }
    }

    public ObservableCollection<DataGridColumn> UserRoleColumns { get; private set; }
}

 

DataSet می تواند در کنار رشته اتصال پایگاه داده برای ذخیره کردن و بازیابی داده ها از  sql server و ... استفاده شود. هر جدول DataSet مجموعه ای از رویداد ها دارد که می تواند برای مطلع شدن از تغییرات داده ها استفاده شود . این مکانیزم برای اضافه کردن و حذف و ویرایش ستونهای داینامیک هنگامی که جدول  role   اصلاح شود استفاده می شود.

public class MainViewModel
{
    public MainViewModel()
    {
        --- Code omitted ---
        this.dataContext = DatabaseContext.Instance;
        this.dataContext.DataSet.Role.RoleRowChanged += this.RoleOnRowChanged;
        this.dataContext.DataSet.Role.RoleRowDeleted += this.RoleOnRoleRowDeleted;
        --- Code omitted ---
    }

    private void RoleOnRowChanged(object sender,
                                  UserRoleDataSet.RoleRowChangeEvent roleRowChangeEvent)
    {
        switch (roleRowChangeEvent.Action)
        {
            case DataRowAction.Change:
                this.UpdateRoleColumn(roleRowChangeEvent.Row);
                break;
            case DataRowAction.Add:
                this.AddRoleColumn(roleRowChangeEvent.Row);
                break;
        }
    }

    private void RoleOnRoleRowDeleted(object sender,
                                      UserRoleDataSet.RoleRowChangeEvent roleRowChangeEvent)
    {
        if (roleRowChangeEvent.Action == DataRowAction.Delete)
        {
            this.DeleteRoleColumn(roleRowChangeEvent.Row);
        }
    }
}

 

منطق کار ی

تعریف ستونهای User data grid  در مجموعه  UserRolesColumn ذخیره می شود. به این معنی است که ستون پیش فرض نیز (نام و نام خانوادگی کاربران ) در آن مجموعه قرار خواهد داشت . برای نام و نام خانوادگی از دو  DataGridTextColumns استفاده کرده ایم.

public class MainViewModel
{
    public MainViewModel()
    {
        this.GenerateDefaultColumns();
        --- Code omitted ---
    }

    private void GenerateDefaultColumns()
    {
        this.UserRoleColumns.Add(new DataGridTextColumn
        {
            Header = "نام", Binding = new Binding("FirstName")
        });
        this.UserRoleColumns.Add(new DataGridTextColumn
        {
            Header = "نام خانوادگی", Binding = new Binding("LastName")
        });
    }
}

تعریف ستونهای داینامیک

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

AddRoleColumn     .  : زمانی فراخوانی می شود که یک نقش به جدول  Role اضافه شود. یک نمونه  DataGridCheckBoxColumn     .  جدید ساخته می شود  ، و  CheckBoxColumnStyle و    UserRoleValueConverter       (پیاده سازی انتساب کاربر به نقش ) اختصاص می یابند.

UpdateRoleColumn     .   : زمانی که محتوای ردیف Role  تغییر کند فراخوانی می شود.

DeleteRole      .   : زمانی که یک نقش از  جدول  Role  حذف شود فراخوانی می شود.

public class MainViewModel
{
    private void AddRoleColumn(UserRoleDataSet.RoleRow role)
    {
        var resourceDictionary = ResourceDictionaryResolver.GetResourceDictionary("Styles.xaml");
        var userRoleValueConverter = resourceDictionary["UserRoleValueConverter"] as IValueConverter;
        var checkBoxColumnStyle = resourceDictionary["CheckBoxColumnStyle"] as Style;
        var binding = new Binding
                          {
                              Converter = userRoleValueConverter,
                              RelativeSource =
                                  new RelativeSource(RelativeSourceMode.FindAncestor,
                                                     typeof(DataGridCell), 1),
                              Path = new PropertyPath("."),
                              Mode = BindingMode.TwoWay
                          };

        var dataGridCheckBoxColumn = new DataGridCheckBoxColumn
                                         {
                                             Header = role.Name,
                                             Binding = binding,
                                             IsThreeState = false,
                                             CanUserSort = false,
                                             ElementStyle = checkBoxColumnStyle,
                                         };

        ObjectTag.SetTag(dataGridCheckBoxColumn, role);
        this.UserRoleColumns.Add(dataGridCheckBoxColumn);
    }

    private void UpdateRoleColumn(UserRoleDataSet.RoleRow role)
    {
        if (role != null)
        {
            foreach (var userRoleColumn in this.UserRoleColumns)
            {
                var roleScan = ColumnTag.GetTag(userRoleColumn) as UserRoleDataSet.RoleRow;
                if (roleScan == role)
                {
                    userRoleColumn.Header = role.Name;
                    break;
                }
            }
        }
    }

    private void DeleteRoleColumn(UserRoleDataSet.RoleRow role)
    {
        if (role != null)
        {
            foreach (var userRoleColumn in this.UserRoleColumns)
            {
                var roleScan = ColumnTag.GetTag(userRoleColumn) as UserRoleDataSet.RoleRow;
                if (roleScan == role)
                {
                    this.UserRoleColumns.Remove(userRoleColumn);
                    break;
                }
            }
        }
    }
}

 

انتساب نقش کاربران

DataGridCheckBoxColumn  چک باکس کنترل را به یک ویژگی boolean از داده ها در ردیفی که نمایش داده می شود اتصال می دهد . در مثال ما یک خاصیت  boolean  در ردیف داده های User خواهد بود که کاربر را به نقش انتساب می دهد . بجای اتصال به کنترل چک باکس یک مبدل مقدار نمونه سازی می شود و به  DataGridCell که شامل کنترل  checkbox  است داده می شود. تعریف  binding  در متد  AddRoleColumn که در بالا نشان داده شد شامل یک انتساب به این مبدل مقدار است.

مبدل مقدار ، متد  Convert را هر بار که  DataGrid تغییر کند فراخوانی میکند ، کاربر و نقش بازیابی می شوند و نتیجه تبدیل را باز می گردانند.

public class UserRoleValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool result = false;
        var dataGridCell = value as DataGridCell;
        if (dataGridCell != null)
        {
            var dataRowView = dataGridCell.DataContext as DataRowView;
            if (dataRowView != null)
            {
                var user = dataRowView.Row as UserRoleDataSet.UserRow;
                var role = ColumnTag.GetTag(dataGridCell.Column) as UserRoleDataSet.RoleRow;

                if (user != null && role != null)
                {
                    var checkBox = dataGridCell.Content as CheckBox;
                    if (checkBox != null)
                    {
                        if (dataGridCell.IsEditing)
                        {
                            checkBox.Checked += this.CheckBoxOnChecked;
                        }
                        else
                        {
                            checkBox.Checked -= this.CheckBoxOnChecked;
                        }
                    }

                    result =
                        DatabaseContext.Instance.DataSet.UserRole.Any(
                            x => x.UserRow == user && x.RoleRow == role);
                }
            }
        }

        return result;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

 

متد  CheckedBoxOnChecked  زمانی فراخوانی می شود که وضعیت  CheckBox تغییر کند . منطق جستجو برای چک باکس  DataGridCell  است و نمونه های کاربر و نقش کاربری که به آن تعلق دارد را می دهد. می تواند ورودی مربوطه  User-role  را  بسته به وضعیت  CheckBox.IsCheckedاضافه یا حذف کند .

rivate void CheckBoxOnChecked(object sender, RoutedEventArgs routedEventArgs)
    {
        var checkBox = sender as CheckBox;
        var dataGridCell = ControlHelper.FindVisualParent<DataGridCell>(checkBox);
        if (dataGridCell != null)
        {
            var dataRowView = dataGridCell.DataContext as DataRowView;
            if (checkBox != null && dataRowView != null)
            {
                var user = dataRowView.Row as UserRoleDataSet.UserRow;
                var role = ObjectTag.GetTag(dataGridCell.Column) as UserRoleDataSet.RoleRow;

                if (user != null && role != null)
                {
                    if (checkBox.IsChecked == true
                        && DatabaseContext.Instance.DataSet.UserRole.Any(
                            x => x.UserRow == user && x.RoleRow == role) == false)
                    {
                        DatabaseContext.Instance.DataSet.UserRole.AddUserRoleRow(user, role);
                    }
                    else
                    {
                        var userRole =
                            DatabaseContext.Instance.DataSet.UserRole.FirstOrDefault(
                                x => x.UserRow == user && x.RoleRow == role);
                        if (userRole != null)
                        {
                            userRole.Delete();
                        }
                    }
                }
            }
        }
    }
}

 

سبک  CheckBox

قابلیتی اضافه کردیم که  کنترل  CheckBox در ردیف آیتم های جدید نشان داده نمی شود. سبک  DataGridCheckBoxColumn  باید تغییر کند ، و Visibility آن باید بسته به محتویات DataGridCell   تنظیم شود ، اگر ردیف داده ، ردیف آیتم جدید باشد آنگاه یک  NewItemPlaceHolder دارد. مبدل برای گرفتن این اطلاعات استفاده می شود. سبک Checkbox در style.xaml تعریف می شود.

پیوست کردن اشیاء  Object tagging

DataColumn  استاندارد در  WPF اجازه برچسب زدن به اشیاء ندارد. Object tagging قابلیتی است که به اشیاء اجازه می دهد به یک کنترل پیوست داده شوند. در جایی استفاده می شود که کنترل در دسترس است اما نمی توان به شیء با استفاده از منطق استاندارد دسترسی داشت . در این مثال کنترل در دسترس  CheckBox  در DataGridCell خواهد بود و شیء مورد نظر  Role  مربوط به ستون خواهد بود . Role  به ستون پیوست داده شده است و می تواند بعدا بازیابی شود.  ObjectTag  خودش یک  DependencyProperty است که می تواند به هر نوعی از کنترل که از DependencyObject مشتق شده است  پیوست داده شود .

ذخیره در دیتابیس

 ذخیره در پایگاه داده را زمانی که داده ها تغییر میکنند پیاده سازی کردیم . فرمان یا  Command به دکمه ذخیره در  Toolbar  اشاره میکند و پایگاه داده را برای تغییرات چک میکند.  DataSet  قابلیت  built-inدارد که محتوا را برای  ویرایش ها تست میکند. اگر  DataSet تغییر کرده باشد دکمه فعال است در غیر اینصورت غیر فعال است .  دستور  CanExecuteChanged  با  CommandMaanager.RequerySuggested  در ارتباط است . وضعیت دکمه هنگامی که برنامه در وضعیت idle  است به صورت خودکار توسط متد  CanExecute چک می شود.

فایل های ضمیمه
دانلود نسخه ی PDF این مطلب