Un tableau à dimensions variables en WPF

Un tableau à dimensions variables en WPF

Lorsque l’on parle de tableau en WPF, on pense à :

  • Une Grid;
  • Un DataGrid;
  • Une ListView.

Ces composants conviennent pour 99% des besoins. Nous sommes loin de l’époque où l’on devait écrire :

  • J’ai 5 lignes;
  • J’ai 4 colonnes.

La magie du databinding est passée par là : désormais, on dispose de tableaux variables, ou plus exactement, à lignes variables. Il suffit de modifier la collection à laquelle est liée la propriété ItemsSource de notre datagrid par exemple, pour ajouter ou retirer une ligne.

Mais voilà, quand vous ne savez pas combien de colonnes votre tableau va comporter, c’est une autre paire de manches !

J’ai bien trouvé à une époque plusieurs solutions :

  • Des dynamiques;
  • Des databinding et des datatemplates alambiqués;
  • Une ListBox, contenant d’autres ListBox.

Aucune de ces solutions ne me satisfaisait réellement. J’ai donc décidé de développer un composant tableau.

Mon besoin :

  • J’ai besoin d’un tableau qui puisse contenir des milliers de lignes et de colonnes;
  • Le scrolling doit être fluide;
  • Je dois pouvoir éditer ou non certaines cellules, de différents types : chaines de caractères, des entiers, des nombres décimaux, des booléens et des dates;
  • Je dois pouvoir sélectionner des lignes ou des colonnes;
  • Lorsque j’édite en masse, je dois pouvoir passer d’une cellule à une autre en validant (touche ENTREE du clavier).

2014-04-16_13h36_37

Dans les grandes lignes, voici ma solution :

  • Je pars d’un UserControl, que je nomme Tableau;
  • J’y insère plusieurs images, des bordures, un scrollviewer, etc.;
  • Je dessine des cellules dans mon image, image que je peux agrandir, réduire etc.;
  • Je crée des structures de données pour gérer mes colonnes, mes lignes et mes cellules;
  • J’ai une TextBox (en fait, une TextBoxInfo, c’est une TextBox avec texte par défaut en surimpression lorsque la cellule est vide) que je vais positionner sur la cellule courante lorsque j’édite. Je gèrerai ensuite les touches de navigation et l’appui sur la touche entrée;
  • Une fois la validation faite, je redessine dans mon image le texte/valeur.

2014-04-16_13h57_51

J’édite ma cellule. Pour cela, j’ai dû setter à True la propriété CanEdit de mon Tableau, mais aussi, fait en sorte que ma cellule, ne soit pas en ReadOnly.

2014-04-16_13h58_08

Lorsque j’initialise mon tableau, je lui définis une structure de colonnes de base, une liste d’index pour les lignes, ainsi que des cellules. Je dois spécifier pour chaque cellule sa couleur, son type (string, entier, decimal, date, etc.). La classe Tableau étant un peu volumineuse, je l’ai splitté en plusieurs fichiers, grâce au mot clé partial. On pourrait sans doute optimiser le code, rajouter des contrôles de type Calendar ou des custom controls, faire du databinding, etc. Libre à vous de vous inspirer de ce composant ! Le composant implémente IDisposable, car la charge mémoire de ce composant est assez importante si vous avez plusieurs milliers de lignes et de colonnes.

<UserControl x:Class="Iguazu.Controls.Tableau.Tableau"
             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:wpf="clr-namespace:PixelLab.Wpf;assembly=PixelLab.Wpf"
             xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300" >
    <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Border x:Name="BdCorner" BorderBrush="Black" BorderThickness="5"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <Image x:Name="myImageCorner"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ></Image>
        </Border>

        <ScrollViewer x:Name="MyHeaderScrollViewer" Grid.Column="1" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
            <Border x:Name="BdHeader" BorderBrush="Black" BorderThickness="5"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <Image x:Name="myImageHeader"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ></Image>
            </Border>
        </ScrollViewer>
        <ScrollViewer x:Name="MyScrollViewer" Grid.Row="1" Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" ScrollChanged="OnScrollChanged">
            <Border x:Name="Bd" BorderBrush="Black" BorderThickness="5"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <Image x:Name="myImageCells"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ></Image>
            </Border>
        </ScrollViewer>
        <Canvas  x:Name="root" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <xctk:ColorPicker x:Name="ColorPicker" Visibility="Collapsed"/>
            <ComboBox x:Name="Combo" ItemsSource="{Binding}" Visibility="Collapsed"/>
            <wpf:InfoTextBox  x:Name="TbInfo" Visibility="Collapsed"/>
        </Canvas>

        <ScrollViewer x:Name="MyIndexScrollViewer"  Grid.Row="1" Grid.Column="0" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
            <Border x:Name="BdIndex" BorderBrush="Black" BorderThickness="5"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <Image x:Name="myImageIndex"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ></Image>
            </Border>
        </ScrollViewer>
    </Grid>
</UserControl>

 

Le code behind, la déclaration de nos propriétés etc. :

/// <summary>
/// Logique d'interaction pour Tableau.xaml
/// </summary>
public partial class Tableau : IDisposable
{
    public delegate void dCelluleUpdate(int noColumn, int noLine, CelluleInfo celluleInfo);
    public event dCelluleUpdate CelluleWasModifiedEvent;

    public delegate void dSomethingHappens();
    public event dSomethingHappens SomethingHappens;

    private bool _CanEdit = false;
    public bool CanEdit
    {
        get { return this._CanEdit; }
        set { this._CanEdit = value;
            if (value)
            {
                this.myImageHeader.MouseDown -= OnClickOnHeader;
                this.myImageCells.MouseDown -= OnEdition;
                this.myImageIndex.MouseDown -= OnClickOnIndex;
                this.timer.Elapsed -= TimerOnElapsed;
                this.TbInfo.PreviewKeyUp -= TbInfoOnPreviewKeyUp;
                this.LostFocus -= OnLostFocus;

                this.myImageHeader.MouseDown += OnClickOnHeader;
                this.myImageCells.MouseDown += OnEdition;
                this.myImageIndex.MouseDown += OnClickOnIndex;
                this.timer.Elapsed -= TimerOnElapsed;
                this.timer.Elapsed += TimerOnElapsed;
                this.TbInfo.PreviewKeyUp += TbInfoOnPreviewKeyUp;
                this.LostFocus += OnLostFocus;
            }
            else
            {
                this.myImageHeader.MouseDown -= OnClickOnHeader;
                this.myImageCells.MouseDown -= OnEdition;
                this.myImageIndex.MouseDown -= OnClickOnIndex;
                this.timer.Elapsed -= TimerOnElapsed;
                this.TbInfo.PreviewKeyUp -= TbInfoOnPreviewKeyUp;
                this.LostFocus -= OnLostFocus;
            }
        }
    }

    private Pen cellPen;
    private int cellPenThickness = 1;
    private int CellHeight;

    private int? selectedColumn;
    private int? selectedLine;
    private int dpiX;
    private int dpiY;
    private int cellIndexWidth;

    private Dictionary<string, Brush> dicoBrushForColorPicker;
    private IDictionary<string, FontFamily> dicoFonts;
    private IDictionary<string, Brush> dicoBrushes;

    private string imageTrue = "pack://application:,,,/Iguazu.Controls.Tableau;component/Images/True.png";
    private string imageFalse = "pack://application:,,,/Iguazu.Controls.Tableau;component/Images/False.png";

    private BitmapImage _ImageTrue;
    private BitmapImage ImageTrue
    {
        get { return this._ImageTrue; }
        set { this._ImageTrue = value; }
    }

    private BitmapImage _ImageFalse;
    private BitmapImage ImageFalse
    {
        get { return this._ImageFalse; }
        set { this._ImageFalse = value; }
    }

    private CelluleInfo _CornerCell;
    internal CelluleInfo CornerCell
    {
        get { return this._CornerCell; }
    }

    private List<ColumnInfo> _ColumnsInfo;
    public List<ColumnInfo> ColumnsInfo
    {
        get { return this._ColumnsInfo; }
        set { this._ColumnsInfo = value; }
    }

    private CelluleInfo[,] _CellsIndex;
    internal CelluleInfo[,] CellsIndex
    {
        get { return this._CellsIndex; }
    }

    private CelluleInfo[,] _Cells;
    internal CelluleInfo[,] Cells
    {
        get { return this._Cells; }
    }

    private int? _CurrentIndexX;
    private int? CurrentIndexX
    {
        get { return this._CurrentIndexX; }
        set { this._CurrentIndexX = value; }
    }

    private int? _CurrentIndexY;
    private int? CurrentIndexY
    {
        get { return this._CurrentIndexY; }
        set { this._CurrentIndexY = value; }
    }

    private Brush _SelectedBrush;
    protected Brush SelectedBrush
    {
        get { return this._SelectedBrush; }
        set { this._SelectedBrush = value; }
    }

    private Brush _SelectedVideoInverseBrush;
    protected Brush SelectedVideoInverseBrush
    {
        get { return this._SelectedVideoInverseBrush; }
        set { this._SelectedVideoInverseBrush = value; }
    }

    private Timer timer;

    private int _NotifyTime;
    public int NotifyTime
    {
        get { return this._NotifyTime; }
        set
        {
            this._NotifyTime = value;
            this.timer.Interval = value;
        }
    }

    public Tableau()
    {
        try
        {
            InitializeComponent();
            this.Init();
        }
        catch (Exception ex)
        {

        }
    }

    private void Init()
    {
        this.cellPen = new Pen(Brushes.Black, 1);
        this.cellPenThickness = 1;
        this.dicoBrushForColorPicker = new Dictionary<string, Brush>();
        this.MyIndexScrollViewer.PreviewMouseWheel += this.DisableScrollViewerOnMouseWheel;
        this.MyHeaderScrollViewer.PreviewMouseWheel += this.DisableScrollViewerOnMouseWheel;

        this.timer = new Timer();
        this.NotifyTime = 1000;
    }

    private List<DateTime>  listNotification = new List<DateTime>();
    private void NotifySomething()
    {
        lock (this)
        {
            this.listNotification.Add(DateTime.Now);
        }

        if (this.isListening)
        {
            this.timer.Start();
        }
    }

    private void TimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
    {
        if (timer == null) return;
        this.timer.Stop();
        lock (this)
        {
            this.listNotification.Clear();
        }

        var handler = this.SomethingHappens;
        if (handler != null) handler();
    }

    private bool isListening;
    public void StartListening()
    {
        this.isListening = true;
        lock (this)
        {
            if (this.listNotification.Count > 0)
            {
                this.timer.Start();
            }
        }
    }

    public void StopListening()
    {
        this.isListening = false;
        if (timer == null) return;
        this.timer.Stop();
    }

    private void OnLostFocus(object sender, RoutedEventArgs routedEventArgs)
    {
        if (!this.CurrentIndexX.HasValue) return;
        if (!this.CurrentIndexY.HasValue) return;
        if (this.TbInfo.IsVisible)
        {
            if (this.TbInfo.IsFocused) return;
            this.UpdateModel();
            return;
        }
        if (this.Combo.IsVisible)
        {
            if (this.Combo.IsFocused) return;
            this.UpdateModel();
        }
        if (this.ColorPicker.IsVisible)
        {
            if (this.ColorPicker.IsFocused) return;
            this.UpdateModel();
        }
    }

    private int? lastColumn;
    private int? lastLine;
    private CelluleInfo lastCelluleInfo;
    private DateTime? lastNotificaiton;
    private void Notify(int noColumn, int noLine, CelluleInfo celluleInfo)
    {
        var dt = DateTime.Now;
        if (lastNotificaiton.HasValue)
        {
            var ts = dt - lastNotificaiton.Value;
            if (ts.TotalSeconds < 1)
            {
                if (lastColumn.HasValue && lastColumn.Value == noColumn)
                {
                    if (lastLine.HasValue && lastLine.Value == noLine)
                    {
                        if (lastCelluleInfo != null && lastCelluleInfo == celluleInfo)
                        {
                            return; //Il y a dejà eu une notification dans la meme seconde
                        }
                    }
                }
            }
        }
        lastNotificaiton = dt;
        lastColumn = noColumn;
        lastLine = noLine;
        lastCelluleInfo = celluleInfo;

        var handler = this.CelluleWasModifiedEvent;
        if (handler != null) handler(noColumn, noLine, celluleInfo);
        this.NotifySomething();
    }

    private void TbInfoOnPreviewKeyUp(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Escape)
        {
            this.HideEdition();
            this.CurrentIndexX = null;
            this.CurrentIndexY = null;
        }
        if (e.Key == Key.Enter)
        {
            ColumnInfo columnInfo = null;
            if (this.CurrentIndexX.HasValue)
            {
                columnInfo = this.ColumnsInfo[this.CurrentIndexX.Value];
            }
            this.UpdateModel();
            if ((columnInfo == null) || !columnInfo.ValidationAndMoveDownInContinue)
            {
                this.HideEdition();
                this.CurrentIndexX = null;
                this.CurrentIndexY = null;
            }
            else
            {
                if ((this.CurrentIndexX.HasValue && this.CurrentIndexY.HasValue))
                {
                    this.CurrentIndexY++;
                    var x = (int) this.RetrieveCellOrigineX(this.CurrentIndexX.Value);
                    this.ShowEditor(this.CurrentIndexX.Value, this.CurrentIndexY.Value, x, false);
                }
            }
        }
        if (e.Key == Key.Up)
        {
            ColumnInfo columnInfo = null;
            if (this.CurrentIndexX.HasValue)
            {
                columnInfo = this.ColumnsInfo[this.CurrentIndexX.Value];
            }
            this.UpdateModel();
            if ((columnInfo == null) || !columnInfo.ValidationAndMoveDownInContinue)
            {
                this.HideEdition();
                this.CurrentIndexX = null;
                this.CurrentIndexY = null;
            }
            else
            {
                if ((this.CurrentIndexX.HasValue && this.CurrentIndexY.HasValue))
                {
                    if (this.CurrentIndexY.Value > 0) this.CurrentIndexY--;
                    var x = (int)this.RetrieveCellOrigineX(this.CurrentIndexX.Value);
                    this.ShowEditor(this.CurrentIndexX.Value, this.CurrentIndexY.Value, x, false);
                }
            }
        }
        if (e.Key == Key.Down)
        {
            ColumnInfo columnInfo = null;
            if (this.CurrentIndexX.HasValue)
            {
                columnInfo = this.ColumnsInfo[this.CurrentIndexX.Value];
            }
            this.UpdateModel();
            if ((columnInfo == null) || !columnInfo.ValidationAndMoveDownInContinue)
            {
                this.HideEdition();
                this.CurrentIndexX = null;
                this.CurrentIndexY = null;
            }
            else
            {
                if ((this.CurrentIndexX.HasValue && this.CurrentIndexY.HasValue))
                {
                    if (this.CurrentIndexY.Value < this.Cells.GetLength(1) - 1) this.CurrentIndexY++;
                    var x = (int)this.RetrieveCellOrigineX(this.CurrentIndexX.Value);
                    this.ShowEditor(this.CurrentIndexX.Value, this.CurrentIndexY.Value, x, false);
                }
            }
        }
    }

    private Brush RetrieveBrush(string brushKey)
    {
        if (!string.IsNullOrWhiteSpace(brushKey) && this.dicoBrushes.ContainsKey(brushKey)) return this.dicoBrushes[brushKey];
        return Brushes.White;
    }

    private FontFamily RetrieveFamily(string fontKey)
    {
        if (string.IsNullOrWhiteSpace(fontKey)) fontKey = "null";
        if (!string.IsNullOrWhiteSpace(fontKey) && this.dicoFonts.ContainsKey(fontKey)) return this.dicoFonts[fontKey];
        return this.FontFamily;
    }

    private void DisableScrollViewerOnMouseWheel(object sender, MouseWheelEventArgs mouseWheelEventArgs)
    {
        mouseWheelEventArgs.Handled = true;
    }

    public void LoadData(CelluleInfo cornerCell, CelluleInfo[,] cellsIndex, int cellIndexWidth, ColumnInfo[] columnsInfo, CelluleInfo[,] cells, int cellHeight, Brush selectedColorBrush, Brush videoInverseBrush, IDictionary<string, FontFamily> dicoFonts, IDictionary<string, Brush> dicoBrushes, string imageTrue = null, string imageFalse = null)
    {
        try
        {
            if (!string.IsNullOrWhiteSpace(imageTrue)) this.imageTrue = imageTrue;
            if (!string.IsNullOrWhiteSpace(imageFalse)) this.imageFalse = imageFalse;
            var uriTrue = new Uri(this.imageTrue);
            var uriFalse = new Uri(this.imageFalse);
            this.ImageTrue = new BitmapImage(uriTrue);
            this.ImageFalse = new BitmapImage(uriFalse);

            this._CornerCell = cornerCell;
            this.dicoBrushForColorPicker.Clear();
            this._CellsIndex = cellsIndex;
            this.cellIndexWidth = cellIndexWidth;
            this._Cells = cells;
            this._ColumnsInfo = new List<ColumnInfo>(columnsInfo);
            if (this._ColumnsInfo.Count != cells.GetLength(0)) throw new Exception(string.Format(CultureInfo.InvariantCulture,  "Vous devez passer en columnWidth autant d'élément que la première dimension du tableau des cellules : {0} tailles, pour des cellules avec {1} colonnes", this.ColumnsInfo.Count, cells.GetLength(0)));

            this.CellHeight = cellHeight;
            this.SelectedBrush = selectedColorBrush;
            this.SelectedVideoInverseBrush = videoInverseBrush;

            this.CheckCellValidity();

            if (dicoFonts == null) this.dicoFonts = new Dictionary<string, FontFamily>();
            else this.dicoFonts = dicoFonts;
            if (!this.dicoFonts.ContainsKey("null")) this.dicoFonts.Add("null", this.FontFamily);

            if (dicoBrushes == null) this.dicoBrushes = new Dictionary<string, Brush>();
            else this.dicoBrushes = dicoBrushes;

            //On rajoute les brushes des colonnes
            foreach (var columnInfo in this.ColumnsInfo)
            {
                this.AddBrushToDico(columnInfo.Foreground);
                this.AddBrushToDico(columnInfo.Background);
            }

            this.dpiX = DeviceHelper.PixelsPerInch(Orientation.Horizontal);
            this.dpiY = DeviceHelper.PixelsPerInch(Orientation.Vertical);
            if (this._CornerCell != null)
            {
                this.DrawCorner();
            }

            if (this.CellsIndex != null)
            {
                this.DrawIndex();
            }

            this.DrawHeader();

            this.DrawAllCells();

            this.CanEdit = true;
        }
        catch (Exception ex)
        {

        }
    }

    private void AddBrushToDico(string colorName)
    {
        if (!string.IsNullOrWhiteSpace(colorName))
        {
            if (!dicoBrushes.ContainsKey(colorName))
            {
                try
                {
                    var color = (Color)ColorConverter.ConvertFromString(colorName);
                    var brush = new SolidColorBrush(color);
                    dicoBrushes.Add(colorName, brush);
                }
                catch (Exception ex)
                {

                }
            }
        }
    }

    private FormattedText MakeHeaderText(ColumnInfo columnInfo, FontFamily fontFamily, Brush foreground, int cellHeight)
    {
        var text = new FormattedText(columnInfo.Name, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface(fontFamily, columnInfo.FontStyle, columnInfo.FontWeight, FontStretches.Normal), columnInfo.FontSize, foreground);
        if (columnInfo.IsUnderlined) text.SetTextDecorations(TextDecorations.Underline);

        text.TextAlignment = TextAlignment.Center;
        text.Trimming = TextTrimming.CharacterEllipsis;
        text.MaxTextWidth = columnInfo.Width - this.cellPenThickness - 1;
        text.MaxTextHeight = cellHeight - this.cellPenThickness - 1;
        return text;
    }

    private void CheckCellValidity()
    {
        for (var i = 0; i < this.Cells.GetLength(0); i++)
        {
            for (var j = 0; j < this.Cells.GetLength(1); j++)
            {
                var cell = this.Cells[i, j];
                switch (cell.CelluleInfoType)
                {
                    case CelluleInfoType.Texte:
                        break;
                    case CelluleInfoType.Entier:
                        {
                            var valeur = 0;
                            var res = int.TryParse(cell.Content, NumberStyles.Any, CultureInfo.InvariantCulture, out valeur);
                            this.UpdateContentAndBadValue(res, cell);
                        }
                        break;
                    case CelluleInfoType.Decimal:
                        {
                            var valeur = 0d;
                            var res = double.TryParse(cell.Content, NumberStyles.Any, CultureInfo.InvariantCulture,
                                                      out valeur);
                            this.UpdateContentAndBadValue(res, cell);
                        }
                        break;
                    case CelluleInfoType.Date:
                        {
                            var valeur = DateTime.Today;
                            var res = DateTime.TryParse(cell.Content, out valeur);
                            this.UpdateContentAndBadValue(res, cell);
                        }
                        break;
                    case CelluleInfoType.Boolean:
                        {
                            if (!cell.BooleanValue.HasValue) cell.BooleanValue = false;
                        }
                        break;
                    case CelluleInfoType.Enumeration:
                        {
                            var columnInfo = this.ColumnsInfo[i];
                            var res = (columnInfo.Enumeration != null) && (columnInfo.Enumeration.Contains(cell.Content));
                            this.UpdateContentAndBadValue(res, cell);
                        }
                        break;
                        break;
                    case CelluleInfoType.Couleur:
                        if (string.IsNullOrWhiteSpace(cell.Content)) continue;
                        if (!this.dicoBrushForColorPicker.ContainsKey(cell.Content))
                        {
                            try
                            {
                                var color = (Color) ColorConverter.ConvertFromString(cell.Content);
                                var brush = new SolidColorBrush(color);
                                this.dicoBrushForColorPicker.Add(cell.Content, brush);
                            }
                            catch (Exception ex)
                            {
                            }
                        }
                        break;
                    case CelluleInfoType.Image:
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }
    }

    private void UpdateContentAndBadValue(bool contentIsOk, CelluleInfo cell)
    {
        if (contentIsOk)
        {
            cell.BadValue = string.Empty;
        }
        else
        {
            cell.BadValue = cell.Content;
            cell.Content = string.Empty;
        }
    }

    private double ComputeTableWidth()
    {
        var width = 0;
        foreach (var columnWidth in this.ColumnsInfo)
        {
            width += columnWidth.Width;
        }
        return width;
    }

    private SolidColorBrush _CurrentColorPickerBrush;
    public Brush CurrentColorPickerBrush
    {
        get { return this._CurrentColorPickerBrush; }
        set { this._CurrentColorPickerBrush = value as SolidColorBrush; }
    }

    private double RetrieveCellOrigineX(int colonne)
    {
        var width = 0d;
        for (var i = 0; i < colonne; i++)
        {
            width += this.ColumnsInfo[i].Width;
        }
        return width;
    }

    private void UpdateModel()
    {
        if (!this.CurrentIndexX.HasValue || !this.CurrentIndexY.HasValue) return;
        if (this.CurrentIndexX.Value >= this.Cells.GetLength(0)) return;
        if (this.CurrentIndexY.Value >= this.Cells.GetLength(1)) return;

        var celluleInfo = this.Cells[this.CurrentIndexX.Value, this.CurrentIndexY.Value];
        if (celluleInfo == null) return;
        if (celluleInfo.IsReadOnly) return;

        var shouldNotify = false;
        switch (celluleInfo.CelluleInfoType)
        {
            case CelluleInfoType.Texte:
                if (this.TbInfo.IsVisible)
                {
                    if (celluleInfo.Content != this.TbInfo.Text) shouldNotify = true;
                    celluleInfo.Content = this.TbInfo.Text;
                }
                break;
            case CelluleInfoType.Entier:
                shouldNotify = this.ProcessCellContentAndBadValueForIntegerCell(celluleInfo);
                break;
            case CelluleInfoType.Decimal:
                shouldNotify = this.ProcessCellContentAndBadValueForDecimalCell(celluleInfo);
                break;
            case CelluleInfoType.Date:
                shouldNotify = this.ProcessCellContentAndBadValueForDateCell(celluleInfo);
                break;
            case CelluleInfoType.Boolean:
                break;
            case CelluleInfoType.Enumeration:
                shouldNotify = this.ProcessCellContentAndBadValueForEnumerationCell(celluleInfo);
                break;
            case CelluleInfoType.Couleur:
                shouldNotify = this.ProcessCellContentAndBadValueForColoredCell(celluleInfo);
                break;
            case CelluleInfoType.Image:
                return;
            default:
                throw new ArgumentOutOfRangeException();
        }
        this.UpdateVisualCurrentCell();
        if (shouldNotify) this.Notify(this.CurrentIndexX.Value, this.CurrentIndexY.Value, celluleInfo);
    }

    private bool ProcessCellContentAndBadValueForBooleanCell(CelluleInfo celluleInfo)
    {
        celluleInfo.BooleanValue = !celluleInfo.BooleanValue;
        return true;
    }

    private bool ProcessCellContentAndBadValueForColoredCell(CelluleInfo celluleInfo)
    {
        var shouldNotify = false;
        if (this.ColorPicker.IsVisible)
        {
            if (this.ColorPicker.SelectedColor != null)
            {
                shouldNotify = (celluleInfo.Content == this.ColorPicker.SelectedColor.ToString());
                celluleInfo.Content = this.ColorPicker.SelectedColor.ToString();
                if (!this.dicoBrushForColorPicker.ContainsKey(celluleInfo.Content))
                {
                    var brush = new SolidColorBrush(this.ColorPicker.SelectedColor);
                    this.dicoBrushForColorPicker.Add(celluleInfo.Content, brush);
                }
            }
            else
            {
                shouldNotify = (celluleInfo.Content != string.Empty);
                celluleInfo.Content = string.Empty;
            }
        }
        return shouldNotify;
    }

    private bool ProcessCellContentAndBadValueForEnumerationCell(CelluleInfo celluleInfo)
    {
        var shouldNotify = (celluleInfo.Content == this.Combo.Text);
        if (this.Combo.IsVisible)
        {
            if (!string.IsNullOrWhiteSpace(this.Combo.Text))
            {
                var columnInfo = this.ColumnsInfo[this.CurrentIndexX.Value];
                var res = (columnInfo.Enumeration != null) && (columnInfo.Enumeration.Contains(this.Combo.Text));
                if (res)
                {
                    celluleInfo.Content = this.Combo.Text;
                    celluleInfo.BadValue = string.Empty;
                }
                else
                {
                    celluleInfo.Content = string.Empty;
                    celluleInfo.BadValue = this.Combo.Text;
                }
            }
            else
            {
                celluleInfo.Content = string.Empty;
            }
        }
        return shouldNotify;
    }

    private bool ProcessCellContentAndBadValueForDateCell(CelluleInfo celluleInfo)
    {
        var shouldNotify = (celluleInfo.Content == this.TbInfo.Text);
        if (this.TbInfo.IsVisible)
        {
            if (!string.IsNullOrWhiteSpace(this.TbInfo.Text))
            {
                var valeur = DateTime.Today;
                var res = DateTime.TryParse(this.TbInfo.Text, out valeur);
                if (res)
                {
                    celluleInfo.Content = this.TbInfo.Text;
                    celluleInfo.BadValue = string.Empty;
                }
                else
                {
                    celluleInfo.Content = string.Empty;
                    celluleInfo.BadValue = this.TbInfo.Text;
                }
            }
            else
            {
                celluleInfo.Content = string.Empty;
            }
        }
        return shouldNotify;
    }

    private bool ProcessCellContentAndBadValueForDecimalCell(CelluleInfo celluleInfo)
    {
        var shouldNotify = (celluleInfo.Content == this.TbInfo.Text);
        if (this.TbInfo.IsVisible)
        {
            if (!string.IsNullOrWhiteSpace(this.TbInfo.Text))
            {
                var valeur = 0d;
                var res = double.TryParse(this.TbInfo.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out valeur);
                if (res)
                {
                    celluleInfo.Content = this.TbInfo.Text;
                    celluleInfo.BadValue = string.Empty;
                }
                else
                {
                    celluleInfo.Content = string.Empty;
                    celluleInfo.BadValue = this.TbInfo.Text;
                }
            }
            else
            {
                celluleInfo.Content = string.Empty;
            }
        }
        return shouldNotify;
    }

    private bool ProcessCellContentAndBadValueForIntegerCell(CelluleInfo celluleInfo)
    {
        var shouldNotify = (celluleInfo.Content == this.TbInfo.Text);
        if (this.TbInfo.IsVisible)
        {
            if (!string.IsNullOrWhiteSpace(this.TbInfo.Text))
            {
                var valeur = 0;
                var res = int.TryParse(this.TbInfo.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out valeur);
                if (res)
                {
                    celluleInfo.Content = this.TbInfo.Text;
                    celluleInfo.BadValue = string.Empty;
                }
                else
                {
                    celluleInfo.Content = string.Empty;
                    celluleInfo.BadValue = this.TbInfo.Text;
                }
            }
            else
            {
                celluleInfo.Content = string.Empty;
            }
        }
        return shouldNotify;
    }

    private void UpdateVisualCurrentCell()
    {
        if ((!this.CurrentIndexX.HasValue) || (!this.CurrentIndexY.HasValue)) return;
        var drawingVisual = new DrawingVisual();
        var drawingContext = drawingVisual.RenderOpen();
        drawingContext.DrawImage(this.myImageCells.Source, new Rect(0, 0, this.myImageCells.Width, this.myImageCells.Height));

        var celluleInfo = this.Cells[this.CurrentIndexX.Value, this.CurrentIndexY.Value];
        var brush = this.RetrieveBrush(celluleInfo.Background);
        var fontFamily = this.RetrieveFamily(celluleInfo.FontFamily);
        var foreground = (string.IsNullOrWhiteSpace(celluleInfo.Foreground)) ? Brushes.Black : this.RetrieveBrush(celluleInfo.Foreground);

        var x = this.RetrieveCellOrigineX(this.CurrentIndexX.Value);
        drawingContext.DrawRectangle(brush, cellPen, new Rect(x, this.CurrentIndexY.Value * this.CellHeight, this.ColumnsInfo[this.CurrentIndexX.Value].Width, this.CellHeight));

        switch (celluleInfo.CelluleInfoType)
        {
            case CelluleInfoType.Texte:
                if (!string.IsNullOrWhiteSpace(celluleInfo.Content))
                {
                    var text = this.MakeText(celluleInfo, fontFamily, foreground, this.ColumnsInfo[this.CurrentIndexX.Value].Width, this.CellHeight);
                    var height = text.Height;
                    var offsetY = (this.CellHeight - height) / 2;
                    drawingContext.DrawText(text, new Point(x + this.cellPenThickness + 1, this.CurrentIndexY.Value * this.CellHeight + this.cellPenThickness + 1 + offsetY));
                }
                break;
            case CelluleInfoType.Entier:
            case CelluleInfoType.Decimal:
            case CelluleInfoType.Date:
            case CelluleInfoType.Enumeration:
                if (!string.IsNullOrWhiteSpace(celluleInfo.BadValue))
                {
                    var text = this.MakeError(celluleInfo, fontFamily, foreground, this.ColumnsInfo[this.CurrentIndexX.Value].Width, this.CellHeight);
                    var height = text.Height;
                    var offsetY = (this.CellHeight - height) / 2;
                    drawingContext.DrawText(text, new Point(x + this.cellPenThickness + 1, this.CurrentIndexY.Value * this.CellHeight + this.cellPenThickness + 1 + offsetY));
                }
                else
                {
                    if (!string.IsNullOrWhiteSpace(celluleInfo.Content))
                    {
                        var text = this.MakeText(celluleInfo, fontFamily, foreground, this.ColumnsInfo[this.CurrentIndexX.Value].Width, this.CellHeight);
                        var height = text.Height;
                        var offsetY = (this.CellHeight - height) / 2;
                        drawingContext.DrawText(text, new Point(x + this.cellPenThickness + 1, this.CurrentIndexY.Value * this.CellHeight + this.cellPenThickness + 1 + offsetY));
                    }
                }
                break;
            case CelluleInfoType.Boolean:
                {
                    if (!celluleInfo.BooleanValue.HasValue) return;
                    var imageSource = this.ImageTrue;
                    if (!celluleInfo.BooleanValue.Value) imageSource = this.ImageFalse;
                    var offsetX = (this.ColumnsInfo[this.CurrentIndexX.Value].Width - imageSource.Width) / 2d;
                    if (offsetX < 0) offsetX = 0;

                    var offsetY = (this.CellHeight - imageSource.Height) / 2d;
                    if (offsetY < 0) offsetY = 0;

                    var width = imageSource.Width;
                    if (width > this.ColumnsInfo[this.CurrentIndexX.Value].Width + offsetX) width = this.ColumnsInfo[this.CurrentIndexX.Value].Width + offsetX;
                    var height = imageSource.Height;
                    if (height > this.CellHeight - offsetY) height = this.CellHeight - offsetY;

                    drawingContext.DrawImage(imageSource, new Rect(x + offsetX, this.CurrentIndexY.Value * this.CellHeight + offsetY, width, height));
                }
                break;
            case CelluleInfoType.Couleur:
                if (dicoBrushForColorPicker.ContainsKey(celluleInfo.Content))
                {
                    var brushC = this.dicoBrushForColorPicker[celluleInfo.Content];
                    drawingContext.DrawRectangle(brushC, this.cellPen, new Rect(x, this.CurrentIndexY.Value * this.CellHeight, this.ColumnsInfo[this.CurrentIndexX.Value].Width, this.CellHeight));
                }
                break;
            case CelluleInfoType.Image:
                if (!string.IsNullOrWhiteSpace(celluleInfo.Content))
                {
                    var uri = new Uri(celluleInfo.ImageUri);
                    var imageSource = new BitmapImage(uri);
                    drawingContext.DrawImage(imageSource, new Rect(x, this.CurrentIndexY.Value * this.CellHeight, this.ColumnsInfo[this.CurrentIndexX.Value].Width, this.CellHeight));
                }
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
        drawingContext.Close();
        var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
        bmp.Render(drawingVisual);
        this.myImageCells.Source = bmp;
    }

    private FormattedText MakeText(CelluleInfo celluleInfo, FontFamily fontFamily, Brush foreground, int cellWidth, int cellHeight)
    {
        var text = new FormattedText(celluleInfo.Content, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface(fontFamily, celluleInfo.FontStyle, celluleInfo.FontWeight, FontStretches.Normal), celluleInfo.FontSize, foreground);
        if (celluleInfo.IsUnderlined) text.SetTextDecorations(TextDecorations.Underline);

        text.TextAlignment = TextAlignment.Center;
        text.Trimming = TextTrimming.CharacterEllipsis;
        text.MaxTextWidth = cellWidth - this.cellPenThickness - 1;
        text.MaxTextHeight = cellHeight - this.cellPenThickness - 1;
        return text;
    }

    private FormattedText MakeError(CelluleInfo celluleInfo, FontFamily fontFamily, Brush foreground, int cellWidth, int cellHeight)
    {
        var text = new FormattedText(celluleInfo.BadValue, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface(fontFamily, celluleInfo.FontStyle, celluleInfo.FontWeight, FontStretches.Normal), celluleInfo.FontSize, Brushes.Red);
        if (celluleInfo.IsUnderlined) text.SetTextDecorations(TextDecorations.Underline);

        text.TextAlignment = TextAlignment.Center;
        text.Trimming = TextTrimming.CharacterEllipsis;
        text.MaxTextWidth = cellWidth - this.cellPenThickness - 1;
        text.MaxTextHeight = cellHeight - this.cellPenThickness - 1;
        return text;
    }

    private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (this.TbInfo.IsVisible)
        {
            var indexX = Canvas.GetLeft(this.TbInfo);
            var indexY = Canvas.GetTop(this.TbInfo);
            var pt = new Point(indexX - e.HorizontalChange, indexY  - e.VerticalChange);
            Canvas.SetLeft(this.TbInfo, pt.X);
            Canvas.SetTop(this.TbInfo, pt.Y);
        }
        if (this.Combo.IsVisible)
        {
            var indexX = Canvas.GetLeft(this.Combo);
            var indexY = Canvas.GetTop(this.Combo);
            var pt = new Point(indexX - e.HorizontalChange, indexY - e.VerticalChange);
            Canvas.SetLeft(this.Combo, pt.X);
            Canvas.SetTop(this.Combo, pt.Y);
        }
        if (this.ColorPicker.IsVisible)
        {
            var indexX = Canvas.GetLeft(this.ColorPicker);
            var indexY = Canvas.GetTop(this.ColorPicker);
            var pt = new Point(indexX - e.HorizontalChange, indexY - e.VerticalChange);
            Canvas.SetLeft(this.ColorPicker, pt.X);
            Canvas.SetTop(this.ColorPicker, pt.Y);
        }
        this.MyIndexScrollViewer.ScrollToVerticalOffset(e.VerticalOffset);
        this.MyHeaderScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);

        var horizontalScrollBarVisibility = this.MyScrollViewer.ComputedHorizontalScrollBarVisibility;
        if (horizontalScrollBarVisibility == Visibility.Visible)
        {
            this.MyIndexScrollViewer.Margin = new Thickness(0, 0, 0, SystemParameters.HorizontalScrollBarHeight);
        }
        else
        {
            this.MyIndexScrollViewer.Margin = new Thickness(0);
        }
        var verticalScrollBarVisibility = this.MyScrollViewer.ComputedVerticalScrollBarVisibility;
        if (verticalScrollBarVisibility == Visibility.Visible)
        {
            this.MyHeaderScrollViewer.Margin = new Thickness(0, 0, SystemParameters.VerticalScrollBarWidth, 0);
        }
        else
        {
            this.MyHeaderScrollViewer.Margin = new Thickness(0);
        }
    }

    private void HideEdition()
    {
        if (this.TbInfo.IsVisible)
        {
            this.TbInfo.Visibility = Visibility.Collapsed;
        }
        if (this.Combo.IsVisible)
        {
            this.Combo.Visibility = Visibility.Collapsed;
        }
        if (this.ColorPicker.IsVisible)
        {
            this.ColorPicker.Visibility = Visibility.Collapsed;
        }
    }

    public CelluleInfo[] RetrieveIndex()
    {
        var res = this._CellsIndex.Clone() as CelluleInfo[];
        return res;
    }

    public CelluleInfo[,] RetrieveCellulesInfo()
    {
        var nbColumns = this.Cells.GetLength(0);
        var nbLines = this.Cells.GetLength(1);
        var res = new CelluleInfo[nbColumns, nbLines];
        for (var i = 0; i < nbColumns; i++)
        {
            for (var j = 0; j < nbLines; j++)
            {
                res[i, j] = this.Cells[i, j];
            }
        }
        return res;
    }

    public List<ColumnInfo> RetrieveColumnsInfo()
    {
        var columns = this.ColumnsInfo;
        return columns;
    }

    public void Reset()
    {
        this.myImageCorner.Width = 10;
        this.myImageCorner.Height = 10;

        this.myImageIndex.Width = 10;
        this.myImageIndex.Height = 10;

        this.myImageHeader.Width = 10;
        this.myImageHeader.Height = 10;

        this.myImageCells.Width = 10;
        this.myImageCells.Height = 10;
    }
}

 

La partie pour dessiner :

partial class Tableau
{
    public void DrawHeader()
    {
        var nbColumns = this.ColumnsInfo.Count;

        myImageHeader.Width = this.ComputeTableWidth();
        myImageHeader.Height = this.CellHeight;
        var drawingVisual = new DrawingVisual();
        var drawingContext = drawingVisual.RenderOpen();
        for (var i = 0; i < nbColumns; i++)
        {
            this.DrawHeaderCell(drawingContext, i);
        }
        drawingContext.Close();
        var bmp = new RenderTargetBitmap((int)myImageHeader.Width, (int)myImageHeader.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
        bmp.Render(drawingVisual);
        this.myImageHeader.Source = bmp;
    }

    private void DrawHeaderCell(DrawingContext drawingContext, int noColonne, Brush background = null, Brush foreground = null)
    {
        var columnInfo = this.ColumnsInfo[noColonne];

        if (background == null)
        {
            background = this.RetrieveBrush(columnInfo.Background);
        }

        var x = this.RetrieveCellOrigineX(noColonne);
        drawingContext.DrawRectangle(background, cellPen, new Rect(x, 0, this.ColumnsInfo[noColonne].Width, this.CellHeight));
        {
            var fontFamily = this.RetrieveFamily(columnInfo.FontFamily);
            if (foreground == null)
            {
                foreground = (string.IsNullOrWhiteSpace(columnInfo.Foreground)) ? Brushes.Black : this.RetrieveBrush(columnInfo.Foreground);
            }

            if (!string.IsNullOrWhiteSpace(columnInfo.Name))
            {
                var text = this.MakeHeaderText(columnInfo, fontFamily, foreground, this.CellHeight);
                var heightText = text.Height;
                var offsetY = (this.CellHeight - heightText) / 2;
                drawingContext.DrawText(text, new Point(x + cellPen.Thickness + 1, cellPen.Thickness + 1 + offsetY));
            }
        }
    }

    public void DrawCorner()
    {
        var drawingVisual = new DrawingVisual();
        var drawingContext = drawingVisual.RenderOpen();

        if (this._CornerCell == null) return;
        var background = this.RetrieveBrush(this._CornerCell.Background);
        drawingContext.DrawRectangle(background, cellPen, new Rect(0, 0, this.cellIndexWidth, this.CellHeight));
        var fontFamily = this.RetrieveFamily(this._CornerCell.FontFamily);

        var foreground = (string.IsNullOrWhiteSpace(this._CornerCell.Foreground)) ? Brushes.Black : this.RetrieveBrush(this._CornerCell.Foreground);

        if (!string.IsNullOrWhiteSpace(this._CornerCell.Content))
        {
            var text = this.MakeText(this._CornerCell, fontFamily, foreground, this.cellIndexWidth, this.CellHeight);
            var heightText = text.Height;
            var offsetY = (this.CellHeight - heightText) / 2;
            drawingContext.DrawText(text, new Point(cellPen.Thickness + 1, cellPen.Thickness + 1 + offsetY));
        }

        drawingContext.Close();
        var bmp = new RenderTargetBitmap(this.cellIndexWidth, this.CellHeight, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
        bmp.Render(drawingVisual);
        this.myImageCorner.Source = bmp;
    }

    public void DrawIndex()
    {
        var nbLines = this.CellsIndex.GetLength(1);

        this.myImageIndex.Width = this.cellIndexWidth;
        this.myImageIndex.Height = this.CellHeight * nbLines;
        if (nbLines == 0) this.myImageIndex.Height = this.CellHeight;

        var drawingVisual = new DrawingVisual();
        var drawingContext = drawingVisual.RenderOpen();

        for (var j = 0; j < nbLines; j++)
        {
            this.DrawIndexCell(drawingContext, j, this.CellsIndex[0, j]);
        }

        drawingContext.Close();
        var bmp = new RenderTargetBitmap((int)myImageIndex.Width, (int)myImageIndex.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
        bmp.Render(drawingVisual);
        this.myImageIndex.Source = bmp;
    }

    private void DrawIndexCell(DrawingContext drawingContext, int ligne, CelluleInfo celluleInfo, Brush background = null, Brush foreground = null)
    {
        if (celluleInfo == null) return;
        if (background == null)
        {
            background = this.RetrieveBrush(celluleInfo.Background);
        }

        drawingContext.DrawRectangle(background, cellPen, new Rect(0, ligne * this.CellHeight, this.cellIndexWidth, this.CellHeight));
        var fontFamily = this.RetrieveFamily(celluleInfo.FontFamily);
        if (foreground == null)
        {
            foreground = (string.IsNullOrWhiteSpace(celluleInfo.Foreground)) ? Brushes.Black : this.RetrieveBrush(celluleInfo.Foreground);
        }

        if (!string.IsNullOrWhiteSpace(celluleInfo.Content))
        {
            var text = this.MakeText(celluleInfo, fontFamily, foreground, this.cellIndexWidth, this.CellHeight);
            var heightText = text.Height;
            var offsetY = (this.CellHeight - heightText) / 2;
            drawingContext.DrawText(text, new Point(cellPen.Thickness + 1, ligne * this.CellHeight + cellPen.Thickness + 1 + offsetY));
        }
    }

    public void DrawAllCells()
    {
        var nbColumns = this.Cells.GetLength(0);
        var nbLines = this.Cells.GetLength(1);

        this.myImageCells.Width = this.ComputeTableWidth();
        this.myImageCells.Height = this.CellHeight * nbLines;
        if (nbLines == 0) this.myImageCells.Height = this.CellHeight;

        var drawingVisual = new DrawingVisual();
        var drawingContext = drawingVisual.RenderOpen();
        for (var i = 0; i < nbColumns; i++)
        {
            for (var j = 0; j < nbLines; j++)
            {
                this.DrawCell(drawingContext, i, j, this.Cells[i, j]);
            }
        }
        drawingContext.Close();
        var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (Double)this.dpiX, (Double)this.dpiY, PixelFormats.Pbgra32);
        bmp.Render(drawingVisual);
        this.myImageCells.Source = bmp;
    }

    private void DrawCell(DrawingContext drawingContext, int colonne, int ligne, CelluleInfo celluleInfo, Brush background = null, Brush foreground = null)
    {
        if (celluleInfo == null) return;
        if (background == null)
        {
            background = this.RetrieveBrush(celluleInfo.Background);
        }

        var x = this.RetrieveCellOrigineX(colonne);
        drawingContext.DrawRectangle(background, cellPen, new Rect(x, ligne * this.CellHeight, this.ColumnsInfo[colonne].Width, this.CellHeight));

        switch (celluleInfo.CelluleInfoType)
        {
            case CelluleInfoType.Texte:
                {
                    var fontFamily = this.RetrieveFamily(celluleInfo.FontFamily);
                    if (foreground == null)
                    {
                        foreground = (string.IsNullOrWhiteSpace(celluleInfo.Foreground)) ? Brushes.Black : this.RetrieveBrush(celluleInfo.Foreground);
                    }

                    if (!string.IsNullOrWhiteSpace(celluleInfo.Content))
                    {
                        var text = this.MakeText(celluleInfo, fontFamily, foreground, this.ColumnsInfo[colonne].Width, this.CellHeight);
                        var heightText = text.Height;
                        var offsetY = (this.CellHeight - heightText) / 2;
                        drawingContext.DrawText(text, new Point(x + cellPen.Thickness + 1, ligne * this.CellHeight + cellPen.Thickness + 1 + offsetY));
                    }
                }
                break;
            case CelluleInfoType.Entier:
            case CelluleInfoType.Decimal:
            case CelluleInfoType.Date:
                {
                    var fontFamily = this.RetrieveFamily(celluleInfo.FontFamily);
                    if (foreground == null)
                    {
                        foreground = (string.IsNullOrWhiteSpace(celluleInfo.Foreground)) ? Brushes.Black : this.RetrieveBrush(celluleInfo.Foreground);
                    }
                    if (!string.IsNullOrWhiteSpace(celluleInfo.BadValue))
                    {
                        var text = this.MakeError(celluleInfo, fontFamily, foreground, this.ColumnsInfo[colonne].Width, this.CellHeight);
                        var heightText = text.Height;
                        var offsetY = (this.CellHeight - heightText) / 2;
                        drawingContext.DrawText(text, new Point(x + cellPen.Thickness + 1, ligne * this.CellHeight + cellPen.Thickness + 1 + offsetY));
                    }
                    else
                    {
                        if (!string.IsNullOrWhiteSpace(celluleInfo.Content))
                        {
                            var text = this.MakeText(celluleInfo, fontFamily, foreground, this.ColumnsInfo[colonne].Width, this.CellHeight);
                            var heightText = text.Height;
                            var offsetY = (this.CellHeight - heightText) / 2;
                            drawingContext.DrawText(text, new Point(x + cellPen.Thickness + 1, ligne * this.CellHeight + cellPen.Thickness + 1 + offsetY));
                        }
                    }
                }
                break;
            case CelluleInfoType.Boolean:
                {
                    if (!celluleInfo.BooleanValue.HasValue) return;
                    var imageSource = this.ImageTrue;
                    if (!celluleInfo.BooleanValue.Value) imageSource = this.ImageFalse;
                    var offsetX = (this.ColumnsInfo[colonne].Width - imageSource.Width) / 2d;
                    if (offsetX < 0) offsetX = 0;

                    var offsetY = (this.CellHeight - imageSource.Height) / 2d;
                    if (offsetY < 0) offsetY = 0;

                    var width = imageSource.Width;
                    if (width > this.ColumnsInfo[colonne].Width + offsetX) width = this.ColumnsInfo[colonne].Width + offsetX;
                    var height = imageSource.Height;
                    if (height > this.CellHeight - offsetY) height = this.CellHeight - offsetY;

                    drawingContext.DrawImage(imageSource, new Rect(x + offsetX, ligne * this.CellHeight + offsetY, width, height));
                }
                break;
            case CelluleInfoType.Enumeration:
                {
                    var fontFamily = this.RetrieveFamily(celluleInfo.FontFamily);
                    if (foreground == null)
                    {
                        foreground = (string.IsNullOrWhiteSpace(celluleInfo.Foreground)) ? Brushes.Black : this.RetrieveBrush(celluleInfo.Foreground);
                    }
                    if (!string.IsNullOrWhiteSpace(celluleInfo.BadValue))
                    {
                        var text = this.MakeError(celluleInfo, fontFamily, foreground, this.ColumnsInfo[colonne].Width, this.CellHeight);
                        var heightText = text.Height;
                        var offsetY = (this.CellHeight - heightText) / 2;
                        drawingContext.DrawText(text, new Point(x + cellPen.Thickness + 1, ligne * this.CellHeight + cellPen.Thickness + 1 + offsetY));
                    }
                    else
                    {
                        if (!string.IsNullOrWhiteSpace(celluleInfo.Content))
                        {
                            var text = this.MakeText(celluleInfo, fontFamily, foreground,
                                                     this.ColumnsInfo[colonne].Width, this.CellHeight);
                            var heightText = text.Height;
                            var offsetY = (this.CellHeight - heightText) / 2;
                            drawingContext.DrawText(text, new Point(x + cellPen.Thickness + 1, ligne * this.CellHeight + cellPen.Thickness + 1 + offsetY));
                        }
                    }
                }
                break;
            case CelluleInfoType.Couleur:
                {
                    if (dicoBrushForColorPicker.ContainsKey(celluleInfo.Content))
                    {
                        var brush = this.dicoBrushForColorPicker[celluleInfo.Content];
                        drawingContext.DrawRectangle(brush, this.cellPen, new Rect(x, ligne * this.CellHeight, this.ColumnsInfo[colonne].Width, this.CellHeight));
                    }
                }
                break;
            case CelluleInfoType.Image:
                {
                    var uri = new Uri(celluleInfo.ImageUri);
                    var imageSource = new BitmapImage(uri);
                    drawingContext.DrawImage(imageSource, new Rect(x, ligne * this.CellHeight, this.ColumnsInfo[colonne].Width, this.CellHeight));
                }
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
    }
}

 

Le code pour enregistrer dans notre bitmap :

partial class Tableau
    {
        public void SaveToBmp(string fileName)
        {
            var encoder = new BmpBitmapEncoder();
            SaveUsingEncoder(fileName, encoder);
        }

        public void SaveToPng(string fileName)
        {
            var encoder = new PngBitmapEncoder();
            SaveUsingEncoder(fileName, encoder);
        }

        // and so on for other encoders (if you want)
        void SaveUsingEncoder(string fileName, BitmapEncoder encoder)
        {
            if (this.selectedColumn.HasValue)
            {
                this.UnSelectColumn(this.selectedColumn.Value);
            }

            if (this.selectedLine.HasValue)
            {
                this.UnSelectLine(this.selectedLine.Value);
            }
            var width = this.myImageIndex.Width + this.myImageCells.Width + 1;
            var height = this.myImageHeader.Height + this.myImageIndex.Height  + 1;
            var bitmap = new RenderTargetBitmap((int)width, (int)height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);

            var drawingVisual = new DrawingVisual();
            var drawingContext = drawingVisual.RenderOpen();
            drawingContext.DrawImage(this.myImageCorner.Source, new Rect(0, 0, this.myImageIndex.Width, this.CellHeight));
            drawingContext.DrawImage(this.myImageHeader.Source, new Rect(myImageIndex.Width + 1, 0, this.myImageHeader.Width, this.myImageHeader.Height));
            drawingContext.DrawImage(this.myImageIndex.Source, new Rect(0, this.myImageHeader.Height + 1, this.myImageIndex.Width, this.myImageIndex.Height));
            drawingContext.DrawImage(this.myImageCells.Source, new Rect(myImageIndex.Width + 1, this.myImageHeader.Height + 1, this.myImageCells.Width, this.myImageCells.Height));
            drawingContext.Close();

            bitmap.Render(drawingVisual);
            var frame = BitmapFrame.Create(bitmap);
            encoder.Frames.Add(frame);

            using (var stream = File.Create(fileName))
            {
                encoder.Save(stream);
            }
        }

        public void SaveCorner(string fileName)
        {
            var encoder = new PngBitmapEncoder();
            var width = this.cellIndexWidth;
            var height = this.CellHeight;
            var bitmap = new RenderTargetBitmap((int)width, (int)height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);

            var drawingVisual = new DrawingVisual();
            var drawingContext = drawingVisual.RenderOpen();
            drawingContext.DrawImage(this.myImageCorner.Source, new Rect(0, 0, this.cellIndexWidth, this.CellHeight));
            drawingContext.Close();

            bitmap.Render(drawingVisual);
            var frame = BitmapFrame.Create(bitmap);
            encoder.Frames.Add(frame);

            using (var stream = File.Create(fileName))
            {
                encoder.Save(stream);
            }
        }

        public void SaveHeader(string fileName)
        {
            var encoder = new PngBitmapEncoder();
            var width = this.myImageHeader.Width;
            var height = this.CellHeight;
            var bitmap = new RenderTargetBitmap((int)width, (int)height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
            var drawingVisual = new DrawingVisual();
            var drawingContext = drawingVisual.RenderOpen();
            drawingContext.DrawImage(this.myImageHeader.Source, new Rect(0, 0, width, height));
            drawingContext.Close();

            bitmap.Render(drawingVisual);
            var frame = BitmapFrame.Create(bitmap);
            encoder.Frames.Add(frame);

            using (var stream = File.Create(fileName))
            {
                encoder.Save(stream);
            }
        }

        public void SaveIndex(string fileName)
        {
            var encoder = new PngBitmapEncoder();
            var width = this.cellIndexWidth;
            var height = this.myImageIndex.Height;
            var bitmap = new RenderTargetBitmap((int)width, (int)height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
            var drawingVisual = new DrawingVisual();
            var drawingContext = drawingVisual.RenderOpen();
            drawingContext.DrawImage(this.myImageIndex.Source, new Rect(0, 0, width, height));
            drawingContext.Close();

            bitmap.Render(drawingVisual);
            var frame = BitmapFrame.Create(bitmap);
            encoder.Frames.Add(frame);

            using (var stream = File.Create(fileName))
            {
                encoder.Save(stream);
            }
        }

        public void SaveCells(string fileName)
        {
            var encoder = new PngBitmapEncoder();
            var width = this.myImageCells.Width;
            var height = this.myImageCells.Height;
            var bitmap = new RenderTargetBitmap((int)width, (int)height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
            var drawingVisual = new DrawingVisual();
            var drawingContext = drawingVisual.RenderOpen();
            drawingContext.DrawImage(this.myImageCells.Source, new Rect(0, 0, width, height));
            drawingContext.Close();

            bitmap.Render(drawingVisual);
            var frame = BitmapFrame.Create(bitmap);
            encoder.Frames.Add(frame);

            using (var stream = File.Create(fileName))
            {
                encoder.Save(stream);
            }
        }

        public void SavePictures()
        {
            this.SaveCorner("Corner.png");
            this.SaveHeader("Header.png");
            this.SaveIndex("Index.png");
            this.SaveCells("Cells.png");
        }

        public void LoadFromImage(string cornerPath, string headerPath, string indexPath, string cellsPath)
        {
            try
            {
                this.CanEdit = false;
                {
                    var image = new BitmapImage();
                    image.BeginInit();
                    image.CacheOption = BitmapCacheOption.OnLoad;
                    image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
                    image.UriSource = new Uri(cornerPath, UriKind.Relative);
                    image.EndInit();
                    this.myImageCorner.Source = image;
                }
                {
                    var image = new BitmapImage();
                    image.BeginInit();
                    image.CacheOption = BitmapCacheOption.OnLoad;
                    image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
                    image.UriSource = new Uri(headerPath, UriKind.Relative);
                    image.EndInit();
                    this.myImageHeader.Source = image;
                }
                {
                    var image = new BitmapImage();
                    image.BeginInit();
                    image.CacheOption = BitmapCacheOption.OnLoad;
                    image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
                    image.UriSource = new Uri(indexPath, UriKind.Relative);
                    image.EndInit();
                    this.myImageIndex.Source = image;
                }
                {
                    var image = new BitmapImage();
                    image.BeginInit();
                    image.CacheOption = BitmapCacheOption.OnLoad;
                    image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
                    image.UriSource = new Uri(cellsPath, UriKind.Relative);
                    image.EndInit();
                    this.myImageCells.Source = image;
                }
            }
            catch (Exception ex)
            {

            }
        }
    }

 

Pour positionner notre Editeur :

partial class Tableau
{
    private void OnEdition(object sender, MouseButtonEventArgs mouseButtonEventArgs)
    {
        bool isDoubleClick = mouseButtonEventArgs.ClickCount >= 2;
        this.UpdateModel();

        var point = mouseButtonEventArgs.GetPosition(sender as Image);
        //On doit retrouver la cellule
        var indexX = 0;
        var width = 0;
        var x = 0;
        for (var i = 0; i < this.ColumnsInfo.Count; i++)
        {
            var next = width + this.ColumnsInfo[i].Width;
            if ((point.X >= width) && (point.X < next))
            {
                indexX = i;
                x = width;
                break;
            }
            else
            {
                width = next;
            }
        }

        var indexY = (int)Math.Floor(point.Y / this.CellHeight);
        this.ShowEditor(indexX, indexY, x, isDoubleClick);
    }

    private void ShowEditor(int indexX, int indexY, int x, bool isDoubleClick)
    {
        if ((this.Cells.GetLength(0) == 0) || (this.Cells.GetLength(1) == 0)) return;
        this.CurrentIndexX = indexX;
        this.CurrentIndexY = indexY;

        this.HideEdition(); //On masque tous les controles d edition
        if ((indexX > this.Cells.GetLength(0)) || (indexY > this.Cells.GetLength(1)))
        {
            return;
        }
        var celluleInfo = this.Cells[indexX, indexY];
        if (celluleInfo == null)
        {
            return;
        }
        if (celluleInfo.IsReadOnly)
        {
            return;
        }

        if (celluleInfo.CelluleInfoType == CelluleInfoType.Image)
        {
            return;
        }

        var pt = new Point(x + this.cellPenThickness + 1 - this.MyScrollViewer.HorizontalOffset,
                           indexY * this.CellHeight + this.cellPenThickness + 1 - this.MyScrollViewer.VerticalOffset);

        switch (celluleInfo.CelluleInfoType)
        {
            case CelluleInfoType.Texte:
                this.ShowTbInfoForTextCell(indexX, celluleInfo, pt);
                break;
            case CelluleInfoType.Entier:
                this.ShowTbInfoForIntegerCell(indexX, celluleInfo, pt);
                break;
            case CelluleInfoType.Decimal:
                this.ShowTbInfoForDoubleCell(indexX, celluleInfo, pt);
                break;
            case CelluleInfoType.Date:
                this.ShowTbInfoForDateCell(indexX, celluleInfo, pt);
                break;
            case CelluleInfoType.Boolean:
                if (isDoubleClick)
                {
                    this.ProcessCellContentAndBadValueForBooleanCell(celluleInfo);
                    this.UpdateVisualCurrentCell();
                }
                break;
            case CelluleInfoType.Enumeration:
                this.ShowComboForEnumerationCell(indexX, celluleInfo, pt);
                break;
            case CelluleInfoType.Couleur:
                this.ShowColorPickerForColoredCell(indexX, celluleInfo, pt);
                break;
            case CelluleInfoType.Image:
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
    }

    private void ShowColorPickerForColoredCell(int noColumn, CelluleInfo celluleInfo, Point pt)
    {
        this.ColorPicker.DataContext = this;
        this.ColorPicker.Width = this.ColumnsInfo[noColumn].Width - 1;
        this.ColorPicker.Height = this.CellHeight - 1;
        this.ColorPicker.FontSize = 12;
        this.ColorPicker.HorizontalContentAlignment = HorizontalAlignment.Center;
        this.ColorPicker.VerticalContentAlignment = VerticalAlignment.Center;
        this.ColorPicker.Visibility = Visibility.Visible;
        var color = Colors.White;

        try
        {
            if (!string.IsNullOrWhiteSpace(celluleInfo.Content))
            {
                color = (Color)ColorConverter.ConvertFromString(celluleInfo.Content);
            }
        }
        catch (Exception)
        {
        }

        this.ColorPicker.SelectedColor = color;
        this.ColorPicker.Focus();
        Canvas.SetLeft(this.ColorPicker, pt.X + this.cellPenThickness + 2);
        Canvas.SetTop(this.ColorPicker, pt.Y + this.cellPenThickness + 2);
    }

    private void ShowComboForEnumerationCell(int noColumn, CelluleInfo celluleInfo, Point pt)
    {
        if (this.CurrentIndexX.HasValue)
        {
            var column = this.ColumnsInfo[this.CurrentIndexX.Value];
            if (column.Enumeration != null)
            {
                this.Combo.DataContext = column.Enumeration;
                this.Combo.Width = this.ColumnsInfo[noColumn].Width - 1;
                this.Combo.Height = this.CellHeight - 1;
                this.Combo.FontSize = celluleInfo.FontSize;
                this.Combo.FontWeight = FontWeights.Bold;
                this.Combo.HorizontalContentAlignment = HorizontalAlignment.Center;
                this.Combo.VerticalContentAlignment = VerticalAlignment.Center;
                this.Combo.Visibility = Visibility.Visible;
                this.Combo.Text = celluleInfo.Content;
                this.Combo.SelectedValue = celluleInfo.Content;
                this.Combo.Focus();
                Canvas.SetLeft(this.Combo, pt.X + this.cellPenThickness + 2);
                Canvas.SetTop(this.Combo, pt.Y + this.cellPenThickness + 2);
            }
        }
    }

    private void ShowTbInfoForDateCell(int noColumn, CelluleInfo celluleInfo, Point pt)
    {
        this.TbInfo.Width = this.ColumnsInfo[noColumn].Width - 2;
        this.TbInfo.Height = this.CellHeight - 2;
        this.TbInfo.TextBoxInfo = this.ColumnsInfo[noColumn].InfoTextForDate;
        this.TbInfo.FontSize = celluleInfo.FontSize;
        this.TbInfo.FontWeight = FontWeights.Bold;
        this.TbInfo.HorizontalContentAlignment = HorizontalAlignment.Center;
        this.TbInfo.VerticalContentAlignment = VerticalAlignment.Center;
        this.TbInfo.Visibility = Visibility.Visible;
        this.TbInfo.Text = celluleInfo.Content;
        this.TbInfo.Focus();
        this.TbInfo.SelectAll();
        Canvas.SetLeft(this.TbInfo, pt.X + this.cellPenThickness + 2);
        Canvas.SetTop(this.TbInfo, pt.Y + this.cellPenThickness + 2);
    }

    private void ShowTbInfoForDoubleCell(int noColumn, CelluleInfo celluleInfo, Point pt)
    {
        this.TbInfo.Width = this.ColumnsInfo[noColumn].Width - 2;
        this.TbInfo.Height = this.CellHeight - 2;
        this.TbInfo.TextBoxInfo = this.ColumnsInfo[noColumn].InfoTextForDouble;
        this.TbInfo.FontSize = celluleInfo.FontSize;
        this.TbInfo.FontWeight = FontWeights.Bold;
        this.TbInfo.HorizontalContentAlignment = HorizontalAlignment.Center;
        this.TbInfo.VerticalContentAlignment = VerticalAlignment.Center;
        this.TbInfo.Visibility = Visibility.Visible;
        this.TbInfo.Text = celluleInfo.Content;
        this.TbInfo.Focus();
        this.TbInfo.SelectAll();
        Canvas.SetLeft(this.TbInfo, pt.X + this.cellPenThickness + 2);
        Canvas.SetTop(this.TbInfo, pt.Y + this.cellPenThickness + 2);
    }

    private void ShowTbInfoForIntegerCell(int noColumn, CelluleInfo celluleInfo, Point pt)
    {
        this.TbInfo.Width = this.ColumnsInfo[noColumn].Width - 2;
        this.TbInfo.Height = this.CellHeight - 2;
        this.TbInfo.TextBoxInfo = this.ColumnsInfo[noColumn].InfoTextForInteger;
        this.TbInfo.FontSize = celluleInfo.FontSize;
        this.TbInfo.FontWeight = FontWeights.Bold;
        this.TbInfo.HorizontalContentAlignment = HorizontalAlignment.Center;
        this.TbInfo.VerticalContentAlignment = VerticalAlignment.Center;
        this.TbInfo.Visibility = Visibility.Visible;
        this.TbInfo.Text = celluleInfo.Content;
        this.TbInfo.Focus();
        this.TbInfo.SelectAll();
        Canvas.SetLeft(this.TbInfo, pt.X + this.cellPenThickness + 2);
        Canvas.SetTop(this.TbInfo, pt.Y + this.cellPenThickness + 2);
    }

    private void ShowTbInfoForTextCell(int noColumn, CelluleInfo celluleInfo, Point pt)
    {
        this.TbInfo.Width = this.ColumnsInfo[noColumn].Width - 2;
        this.TbInfo.Height = this.CellHeight - 2;
        this.TbInfo.TextBoxInfo = this.ColumnsInfo[noColumn].InfoTextForText;
        this.TbInfo.FontSize = celluleInfo.FontSize;
        this.TbInfo.FontWeight = FontWeights.Bold;
        this.TbInfo.HorizontalContentAlignment = HorizontalAlignment.Center;
        this.TbInfo.VerticalContentAlignment = VerticalAlignment.Center;
        this.TbInfo.Visibility = Visibility.Visible;
        this.TbInfo.Text = celluleInfo.Content;
        this.TbInfo.Focus();
        this.TbInfo.SelectAll();
        Canvas.SetLeft(this.TbInfo, pt.X + this.cellPenThickness + 2);
        Canvas.SetTop(this.TbInfo, pt.Y + this.cellPenThickness + 2);
    }
}

 

La gestion du IDisposable :

partial class Tableau
{
    private bool disposed = false;
    public void Dispose()
    {
        this.Dispose(true);
        // This object will be cleaned up by the Dispose method.
        // Therefore, you should call GC.SupressFinalize to
        // take this object off the finalization queue
        // and prevent finalization code for this object
        // from executing a second time.
        GC.SuppressFinalize(this);
    }

    // Dispose(bool disposing) executes in two distinct scenarios.
    // If disposing equals true, the method has been called directly
    // or indirectly by a user's code. Managed and unmanaged resources
    // can be disposed.
    // If disposing equals false, the method has been called by the
    // runtime from inside the finalizer and you should not reference
    // other objects. Only unmanaged resources can be disposed.
    protected virtual void Dispose(bool disposing)
    {
        // Check to see if Dispose has already been called.
        if (!this.disposed)
        {
            // If disposing equals true, dispose all managed
            // and unmanaged resources.
            if (disposing)
            {
                // Dispose managed resources.
                try
                {
                    if (this.timer != null)
                    {
                        this.timer.Stop();
                        this.timer.Elapsed -= TimerOnElapsed;
                    }

                    if (this.MyIndexScrollViewer != null)
                    {
                        this.MyIndexScrollViewer.PreviewMouseWheel -= this.DisableScrollViewerOnMouseWheel;
                    }
                    if (this.MyHeaderScrollViewer != null)
                    {
                        this.MyHeaderScrollViewer.PreviewMouseWheel -= this.DisableScrollViewerOnMouseWheel;
                    }
                    if (this.TbInfo != null)
                    {
                        this.TbInfo.PreviewKeyUp -= TbInfoOnPreviewKeyUp;
                    }

                    if (this.myImageCorner != null)
                    {
                        this.myImageCorner.Source = null;
                        this.myImageCorner.Width = 0;
                        this.myImageIndex.Height = 0;
                        this.myImageCorner = null;
                    }

                    if (this.myImageIndex != null)
                    {
                        this.myImageIndex.MouseDown -= OnClickOnIndex;
                        this.myImageIndex.Source = null;
                        this.myImageIndex.Width = 0;
                        this.myImageIndex.Height = 0;
                        this.myImageIndex = null;
                    }
                    if (this.myImageHeader != null)
                    {
                        this.myImageHeader.MouseDown -= OnClickOnHeader;
                        this.myImageHeader.Source = null;
                        this.myImageHeader.Width = 0;
                        this.myImageHeader.Height = 0;
                        this.myImageHeader = null;
                    }
                    if (this.myImageCells != null)
                    {
                        this.myImageCells.MouseDown -= OnEdition;
                        this.myImageCells.Source = null;
                        this.myImageCells.Width = 0;
                        this.myImageCells.Height = 0;
                        this.myImageCells = null;
                    }
                    this.Width = 0;
                    this.Height = 0;

                    this.LostFocus -= OnLostFocus;

                    if (this.dicoBrushForColorPicker != null)
                    {
                        if (this.dicoBrushForColorPicker.Keys.Count > 0)
                        {
                            var keys = dicoBrushForColorPicker.Keys.Select(k => k.ToString()).ToList();
                            for (var i = keys.Count() - 1; i >= 0; i--)
                            {
                                var key = keys[i].ToString();
                                dicoBrushForColorPicker[key] = null;
                            }
                        }

                        dicoBrushForColorPicker.Clear();
                        dicoBrushForColorPicker = null;
                    }

                    if (this.dicoFonts != null)
                    {
                        if (this.dicoFonts.Keys.Count > 0)
                        {
                            var keys = dicoFonts.Keys.Select(k => k.ToString()).ToList();
                            for (var i = keys.Count() - 1; i >= 0; i--)
                            {
                                var key = keys[i].ToString();
                                dicoFonts[key] = null;
                            }
                        }

                        dicoFonts.Clear();
                        dicoFonts = null;
                    }

                    if (this.dicoBrushes != null)
                    {
                        if (this.dicoBrushes.Keys.Count > 0)
                        {
                            var keys = dicoBrushes.Keys.Select(k => k.ToString()).ToList();
                            for (var i = keys.Count() - 1; i >= 0; i--)
                            {
                                var key = keys[i].ToString();
                                dicoBrushes[key] = null;
                            }
                        }

                        this.dicoBrushes.Clear();
                        this.dicoBrushes = null;
                    }

                    this._SelectedBrush = null;
                    this._SelectedVideoInverseBrush = null;

                    this._CellsIndex = null;
                    this._Cells = null;
                    this._ColumnsInfo = null;
                    if (this.listNotification != null)
                    {
                        this.listNotification.Clear();
                        this.listNotification = null;
                    }
                }
                catch (Exception ex)
                {

                }
            }

            // Call the appropriate methods to clean up
            // unmanaged resources here.
            // If disposing is false,
            disposed = true;
        }
    }

    ~Tableau()
    {
        // Do not re-create Dispose clean-up code here.
        // Calling Dispose(false) is optimal in terms of
        // readability and maintainability.
        this.Dispose(false);
    }
}

 

Pour ajouter des colonnes :

partial class Tableau
{
    public void AddColumn(ColumnInfo columnWidth, CelluleInfo header = null)
    {
        //MAJ du model
        this.CurrentIndexX = null;
        this.CurrentIndexY = null;
        this.HideEdition();
        this.ColumnsInfo.Add(columnWidth);

        var nbColumns = this.Cells.GetLength(0);
        var nbLines = this.Cells.GetLength(1);

        var tmpArray = new CelluleInfo[nbColumns + 1, nbLines];
        for (var i = 0; i < nbColumns; i++)
        {
            for (var j = 0; j < nbLines; j++)
            {
                tmpArray[i, j] = this.Cells[i, j];
            }
        }
        for (var j = 1; j < nbLines; j++)
        {
            tmpArray[nbColumns, j] = new CelluleInfo();
        }

        this._Cells = tmpArray;

        if (header != null)
        {
            this.Cells[nbColumns, 0] = header;
        }
        else this.Cells[nbColumns, 0] = new CelluleInfo();

        //MAJ de l'affichage
        var drawingVisual = new DrawingVisual();
        var drawingContext = drawingVisual.RenderOpen();
        drawingContext.DrawImage(this.myImageCells.Source, new Rect(0, 0, this.myImageCells.Width, this.myImageCells.Height));
        this.myImageCells.Width = this.ComputeTableWidth();

        for (var j = 0; j < nbLines; j++)
        {
            this.DrawCell(drawingContext, nbColumns, j, this.Cells[nbColumns, j]);
        }

        drawingContext.Close();
        var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
        bmp.Render(drawingVisual);
        this.myImageCells.Source = bmp;
    }

    public void DeleteColumn(int index)
    {
        //MAJ du model
        this.CurrentIndexX = null;
        this.CurrentIndexY = null;
        this.HideEdition();
        var nbColumns = this.Cells.GetLength(0);
        var nbLines = this.Cells.GetLength(1);

        if ((index < 0) || (index >= nbColumns)) return;
        if (this.ColumnsInfo.Count == 1) return;
        this.ColumnsInfo.RemoveAt(index);

        var tmpArray = new CelluleInfo[nbColumns - 1, nbLines];
        var col = 0;
        for (var i = 0; i < nbColumns; i++)
        {
            if (i == index) continue;
            for (var j = 0; j < nbLines; j++)
            {
                tmpArray[col, j] = this.Cells[i, j];
            }
            col++;
        }

        this._Cells = tmpArray;

        //MAJ de l'affichage
        var drawingVisual = new DrawingVisual();
        var drawingContext = drawingVisual.RenderOpen();
        this.myImageCells.Width = this.ComputeTableWidth();

        for (var i = 0; i < this.Cells.GetLength(0); i++)
        {
            for (var j = 0; j < this.Cells.GetLength(1); j++)
            {
                this.DrawCell(drawingContext, i, j, this.Cells[i, j]);
            }
        }

        drawingContext.Close();
        var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
        bmp.Render(drawingVisual);
        this.myImageCells.Source = bmp;
    }

    public void SwapColumns(int index0, int index1)
    {
        this.CurrentIndexX = null;
        this.CurrentIndexY = null;
        this.HideEdition();

        if (index0 == index1) return;
        if (index0 < 0) return;
        if (index1 < 0) return;
        var nbColumns = this.Cells.GetLength(0);
        if (index0 >= nbColumns) return;
        if (index1 >= nbColumns) return;

        //MAJ du model
        var tmpWidth = this.ColumnsInfo[index0];
        this.ColumnsInfo[index0] = this.ColumnsInfo[index1];
        this.ColumnsInfo[index1] = tmpWidth;

        CelluleInfo tmp = null;
        for (var j = 0; j < this.Cells.GetLength(1); j++)
        {
            tmp = this.Cells[index0, j];
            this.Cells[index0, j] = this.Cells[index1, j];
            this.Cells[index1, j] = tmp;
        }

        //On est obligé de tout redessinné, car les tailles de colonnes peuvent differer entre les deux colonnes à permutter
        var drawingVisual = new DrawingVisual();
        var drawingContext = drawingVisual.RenderOpen();
        for (var i = 0; i < nbColumns; i++)
        {
            if ((i == index0) || (i == index1)) continue;
            for (var j = 0; j < this.Cells.GetLength(1); j++)
            {
                this.DrawCell(drawingContext, i, j, this.Cells[i, j]);
            }
        }

        for (var j = 0; j < this.Cells.GetLength(1); j++)
        {
            this.DrawCell(drawingContext, index0, j, this.Cells[index0, j]);
            this.DrawCell(drawingContext, index1, j, this.Cells[index1, j]);
        }
        drawingContext.Close();
        var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
        bmp.Render(drawingVisual);
        this.myImageCells.Source = bmp;
    }
}

 

Pour ajouter des lignes :

partial class Tableau
{
   public void AddLine(CelluleInfo indexCellule = null)
   {
       //MAJ du model
       this.CurrentIndexX = null;
       this.CurrentIndexY = null;
       this.HideEdition();
       var nbColumns = this.Cells.GetLength(0);
       var nbLines = this.Cells.GetLength(1);

       var tmpArray = new CelluleInfo[nbColumns, nbLines + 1];
       var tmpIndexArray = new CelluleInfo[1, nbLines + 1];
       for (var i = 0; i < nbColumns; i++)
       {
           for (var j = 0; j < nbLines; j++)
           {
               tmpArray[i, j] = this.Cells[i, j];
           }
       }
       for (var j = 0; j < nbLines; j++)
       {
           tmpIndexArray[0, j] = this.CellsIndex[0, j];
       }
       this._CellsIndex = tmpIndexArray;

       for (var i = 0; i < nbColumns; i++)
       {
           tmpArray[i, nbLines] = new CelluleInfo();
       }

       this._Cells = tmpArray;

       if (indexCellule != null)
       {
           this.CellsIndex[0, nbLines] = indexCellule;
       }
       else this.CellsIndex[0, nbLines] = new CelluleInfo();
       //MAJ de l'affichage
       {
           var drawingVisualIndex = new DrawingVisual();
           var drawingContextIndex = drawingVisualIndex.RenderOpen();
           drawingContextIndex.DrawImage(this.myImageIndex.Source,
                                         new Rect(0, 0, this.myImageIndex.Width, this.myImageIndex.Height));
           this.myImageIndex.Height = (nbLines + 1) * this.CellHeight;
           this.DrawIndexCell(drawingContextIndex, nbLines, this.CellsIndex[0, nbLines]);
           drawingContextIndex.Close();
           var bmpIndex = new RenderTargetBitmap((int)myImageIndex.Width, (int)myImageIndex.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
           bmpIndex.Render(drawingVisualIndex);
           this.myImageIndex.Source = bmpIndex;
       }
       {
           var drawingVisual = new DrawingVisual();
           var drawingContext = drawingVisual.RenderOpen();
           drawingContext.DrawImage(this.myImageCells.Source,
                                         new Rect(0, 0, this.myImageCells.Width, this.myImageCells.Height));
           this.myImageCells.Height = (nbLines + 1) * this.CellHeight;
           for (var i = 0; i < nbColumns; i++)
           {
               this.DrawCell(drawingContext, i, nbLines, this.Cells[0, nbLines]);
           }
           drawingContext.Close();
           var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
           bmp.Render(drawingVisual);
           this.myImageCells.Source = bmp;
       }
   }

   public void DeleteLine(int index)
   {
       //MAJ du model
       this.CurrentIndexX = null;
       this.CurrentIndexY = null;
       this.HideEdition();
       var nbColumns = this.Cells.GetLength(0);
       var nbLines = this.Cells.GetLength(1);

       if ((index < 0) || (index >= nbLines)) return;
       if (nbLines == 1) return;

       var tmpArrayIndex = new CelluleInfo[1, nbLines - 1];
       var tmpArray = new CelluleInfo[nbColumns, nbLines - 1];
       var line = 0;
       for (var j = 0; j < nbLines; j++)
       {
           if (j == index) continue;
           tmpArrayIndex[0, line] = this.CellsIndex[0, j];
           line++;
       }

       for (var i = 0; i < nbColumns; i++)
       {
           line = 0;
           for (var j = 0; j < nbLines; j++)
           {
               if (j == index) continue;
               tmpArray[i, line] = this.Cells[i, j];
               line++;
           }
       }
       this._CellsIndex = tmpArrayIndex;
       this._Cells = tmpArray;

       //MAJ de l'affichage
       {
           var drawingVisualIndex = new DrawingVisual();
           var drawingContextIndex = drawingVisualIndex.RenderOpen();
           this.myImageIndex.Height = (nbLines - 1) * this.CellHeight;

           for (var j = 1; j < this.CellsIndex.GetLength(1); j++)
           {
               this.DrawIndexCell(drawingContextIndex, j, this.CellsIndex[0, j]);
           }

           drawingContextIndex.Close();
           var bmpIndex = new RenderTargetBitmap((int)myImageIndex.Width, (int)myImageIndex.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
           bmpIndex.Render(drawingVisualIndex);
           this.myImageIndex.Source = bmpIndex;
       }
       {
           var drawingVisual = new DrawingVisual();
           var drawingContext = drawingVisual.RenderOpen();
           this.myImageCells.Height = (nbLines - 1) * this.CellHeight;

           for (var i = 0; i < this.Cells.GetLength(0); i++)
           {
               for (var j = 0; j < this.Cells.GetLength(1); j++)
               {
                   this.DrawCell(drawingContext, i, j, this.Cells[i, j]);
               }
           }
           drawingContext.Close();
           var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
           bmp.Render(drawingVisual);
           this.myImageCells.Source = bmp;
       }
   }

   public void SwapLines(int index0, int index1)
   {
       this.CurrentIndexX = null;
       this.CurrentIndexY = null;
       this.HideEdition();

       if (index0 == index1) return;
       if (index0 < 0) return;
       if (index1 < 0) return;
       var nbLines = this.Cells.GetLength(1);
       if (index0 >= nbLines) return;
       if (index1 >= nbLines) return;

       //MAJ du model
       CelluleInfo tmp = null;
       tmp = this.CellsIndex[0, index0];
       this.CellsIndex[0, index0] = this.CellsIndex[0, index1];
       this.CellsIndex[0, index1] = tmp;

       for (var i = 0; i < this.Cells.GetLength(0); i++)
       {
           tmp = this.Cells[i, index0];
           this.Cells[i, index0] = this.Cells[i, index1];
           this.Cells[i, index1] = tmp;
       }

       //Comme les hauteurs des lignes ne changent pas, on peut recopier les images
       {
           var drawingVisualIndex = new DrawingVisual();
           var drawingContextIndex = drawingVisualIndex.RenderOpen();
           drawingContextIndex.DrawImage(this.myImageIndex.Source, new Rect(0, 0, this.myImageIndex.Width, this.myImageIndex.Height));

           this.DrawIndexCell(drawingContextIndex, index0, this.CellsIndex[0, index0]);
           this.DrawIndexCell(drawingContextIndex, index1, this.CellsIndex[0, index1]);

           drawingContextIndex.Close();
           var bmpIndex = new RenderTargetBitmap((int)myImageIndex.Width, (int)myImageIndex.Height, (double)this.dpiX, (double)this.dpiY,
                                            PixelFormats.Pbgra32);
           bmpIndex.Render(drawingVisualIndex);
           this.myImageIndex.Source = bmpIndex;
       }
       {
           var drawingVisual = new DrawingVisual();
           var drawingContext = drawingVisual.RenderOpen();
           drawingContext.DrawImage(this.myImageCells.Source, new Rect(0, 0, this.myImageCells.Width, this.myImageCells.Height));

           for (var i = 0; i < this.Cells.GetLength(0); i++)
           {
               this.DrawCell(drawingContext, i, index0, this.Cells[i, index0]);
               this.DrawCell(drawingContext, i, index1, this.Cells[i, index1]);
           }

           drawingContext.Close();
           var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY,
                                            PixelFormats.Pbgra32);
           bmp.Render(drawingVisual);
           this.myImageCells.Source = bmp;
       }
   }
}

 

Pour sélectionner des lignes ou des colonnes :

partial class Tableau
{
    private void OnClickOnHeader(object sender, MouseButtonEventArgs mouseButtonEventArgs)
    {
        var point = mouseButtonEventArgs.GetPosition(sender as Image);
        //On doit retrouver la cellule
        var indexX = 0;
        var width = 0;
        var x = 0;
        for (var i = 0; i < this.ColumnsInfo.Count; i++)
        {
            var next = width + this.ColumnsInfo[i].Width;
            if ((point.X >= width) && (point.X < next))
            {
                indexX = i;
                x = width;
                break;
            }
            else
            {
                width = next;
            }
        }

        if (this.selectedLine.HasValue)
        {
            this.UnSelectLine(this.selectedLine.Value);
        }

        var background = this.RetrieveBrush(ColumnsInfo[indexX].Background);
        var foreground = (string.IsNullOrWhiteSpace(ColumnsInfo[indexX].Foreground)) ? Brushes.Black : this.RetrieveBrush(ColumnsInfo[indexX].Foreground);
        this.SelectColumn(indexX, background, foreground);
    }

    private void OnClickOnIndex(object sender, MouseButtonEventArgs mouseButtonEventArgs)
    {
        var point = mouseButtonEventArgs.GetPosition(sender as Image);
        var indexY = (int)Math.Floor(point.Y / this.CellHeight);

        if (this.selectedColumn.HasValue)
        {
            this.UnSelectColumn(this.selectedColumn.Value);
        }
        this.SelectLine(indexY, this.SelectedBrush, this.SelectedVideoInverseBrush);
    }

    public void SelectColumn(int index, Brush background, Brush foreground)
    {
        this.CurrentIndexX = null;
        this.CurrentIndexY = null;
        this.HideEdition();
        //MAJ du model
        if (this.selectedColumn.HasValue)
        {
            this.UnSelectColumn(this.selectedColumn.Value);
        }
        this.selectedColumn = index;

        var nbColumns = this.Cells.GetLength(0);

        if ((index < 0) || (index >= nbColumns)) return;
        {
            var drawingVisual = new DrawingVisual();
            var drawingContext = drawingVisual.RenderOpen();
            drawingContext.DrawImage(this.myImageCells.Source, new Rect(0, 0, this.myImageCells.Width, this.myImageCells.Height));

            for (var i = 0; i < this.Cells.GetLength(1); i++)
            {
                this.DrawCell(drawingContext, index, i, this.Cells[index, i], background, foreground);
            }
            drawingContext.Close();
            var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
            bmp.Render(drawingVisual);
            this.myImageCells.Source = bmp;
        }
    }

    public void UnSelectColumn(int index)
    {
        this.CurrentIndexX = null;
        this.CurrentIndexY = null;
        this.HideEdition();
        //MAJ du model
        var nbColumns = this.Cells.GetLength(0);
        this.selectedColumn = null;

        if ((index < 0) || (index >= nbColumns)) return;
        {
            var drawingVisual = new DrawingVisual();
            var drawingContext = drawingVisual.RenderOpen();
            drawingContext.DrawImage(this.myImageCells.Source, new Rect(0, 0, this.myImageCells.Width, this.myImageCells.Height));

            for (var i = 0; i < this.Cells.GetLength(1); i++)
            {
                this.DrawCell(drawingContext, index, i, this.Cells[index, i]);
            }
            drawingContext.Close();
            var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
            bmp.Render(drawingVisual);
            this.myImageCells.Source = bmp;
        }
    }

    public void SelectLine(int index, Brush background, Brush foreground)
    {
        this.CurrentIndexX = null;
        this.CurrentIndexY = null;
        this.HideEdition();
        if (this.selectedLine.HasValue)
        {
            this.UnSelectLine(this.selectedLine.Value);
        }
        this.selectedLine = index;

        //MAJ du model
        var nbLines = this.Cells.GetLength(1);

        if ((index < 0) || (index >= nbLines)) return;
        {
            var drawingVisualIndex = new DrawingVisual();
            var drawingContextIndex = drawingVisualIndex.RenderOpen();
            drawingContextIndex.DrawImage(this.myImageIndex.Source, new Rect(0, 0, this.myImageIndex.Width, this.myImageIndex.Height));

            this.DrawIndexCell(drawingContextIndex, index, this.CellsIndex[0, index], background, foreground);

            drawingContextIndex.Close();
            var bmpIndex = new RenderTargetBitmap((int)myImageIndex.Width, (int)myImageIndex.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
            bmpIndex.Render(drawingVisualIndex);
            this.myImageIndex.Source = bmpIndex;
        }
        {
            var drawingVisual = new DrawingVisual();
            var drawingContext = drawingVisual.RenderOpen();
            drawingContext.DrawImage(this.myImageCells.Source, new Rect(0, 0, this.myImageCells.Width, this.myImageCells.Height));

            for (var i = 0; i < this.Cells.GetLength(0); i++)
            {
                this.DrawCell(drawingContext, i, index, this.Cells[i, index], background, foreground);
            }
            drawingContext.Close();
            var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
            bmp.Render(drawingVisual);
            this.myImageCells.Source = bmp;
        }
    }

    public void UnSelectLine(int index)
    {
        this.CurrentIndexX = null;
        this.CurrentIndexY = null;
        this.HideEdition();
        //MAJ du model
        var nbLines = this.Cells.GetLength(1);
        this.selectedLine = null;

        if ((index < 0) || (index >= nbLines)) return;
        {
            var drawingVisualIndex = new DrawingVisual();
            var drawingContextIndex = drawingVisualIndex.RenderOpen();
            drawingContextIndex.DrawImage(this.myImageIndex.Source, new Rect(0, 0, this.myImageIndex.Width, this.myImageIndex.Height));

            this.DrawIndexCell(drawingContextIndex, index, this.CellsIndex[0, index]);

            drawingContextIndex.Close();
            var bmpIndex = new RenderTargetBitmap((int)myImageIndex.Width, (int)myImageIndex.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
            bmpIndex.Render(drawingVisualIndex);
            this.myImageIndex.Source = bmpIndex;
        }
        {
            var drawingVisual = new DrawingVisual();
            var drawingContext = drawingVisual.RenderOpen();
            drawingContext.DrawImage(this.myImageCells.Source, new Rect(0, 0, this.myImageCells.Width, this.myImageCells.Height));

            for (var i = 0; i < this.Cells.GetLength(0); i++)
            {
                this.DrawCell(drawingContext, i, index, this.Cells[i, index]);
            }
            drawingContext.Close();
            var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY, PixelFormats.Pbgra32);
            bmp.Render(drawingVisual);
            this.myImageCells.Source = bmp;
        }
    }
}

 

Pour mettre à jour une cellule, une ligne etc. :

partial class Tableau
{
    public void UpdateCell(int noColumn, int noLine, CelluleInfo celluleInfo)
    {
        //MAJ du model
        this.CurrentIndexX = null;
        this.CurrentIndexY = null;
        this.HideEdition();

        if (noLine < 0) return;
        if (noColumn < 0) return;
        var nbColonnes = this.Cells.GetLength(0);
        var nbLines = this.Cells.GetLength(1);
        if (noColumn >= nbColonnes) return;
        if (noLine >= nbLines) return;

        this.Cells[noColumn, noLine] = celluleInfo;

        var drawingVisual = new DrawingVisual();
        var drawingContext = drawingVisual.RenderOpen();
        drawingContext.DrawImage(this.myImageCells.Source, new Rect(0, 0, this.myImageCells.Width, this.myImageCells.Height));
        this.DrawCell(drawingContext, noColumn, noLine, this.Cells[noColumn, noLine]);

        drawingContext.Close();
        var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY,
                                         PixelFormats.Pbgra32);
        bmp.Render(drawingVisual);
        this.myImageCells.Source = bmp;
    }

    public void UpdateLine(int noLine, CelluleInfo[] cellLine)
    {
        //MAJ du model
        this.CurrentIndexX = null;
        this.CurrentIndexY = null;
        this.HideEdition();

        if (noLine < 0) return;
        var nbColonnes = this.Cells.GetLength(0);
        var nbLines = this.Cells.GetLength(1);
        if (cellLine.Length != nbColonnes) return;
        if (noLine >= nbLines) return;

        for (var i = 0; i < cellLine.Length; i++)
        {
            this.Cells[i, noLine] = cellLine[i];
        }

        var drawingVisual = new DrawingVisual();
        var drawingContext = drawingVisual.RenderOpen();
        drawingContext.DrawImage(this.myImageCells.Source, new Rect(0, 0, this.myImageCells.Width, this.myImageCells.Height));

        for (var i = 0; i < cellLine.Length; i++)
        {
            this.DrawCell(drawingContext, i, noLine, this.Cells[i, noLine]);
        }

        drawingContext.Close();
        var bmp = new RenderTargetBitmap((int)myImageCells.Width, (int)myImageCells.Height, (double)this.dpiX, (double)this.dpiY,
                                         PixelFormats.Pbgra32);
        bmp.Render(drawingVisual);
        this.myImageCells.Source = bmp;
    }
}

 

Enfin, nos structures de données :

public class CelluleInfo
{
    protected bool Equals(CelluleInfo other)
    {
        return string.Equals(Content, other.Content) && _IsReadOnly.Equals(other._IsReadOnly) && _CelluleInfoType == other._CelluleInfoType && string.Equals(_ImageUri, other._ImageUri) && string.Equals(_BadValue, other._BadValue);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((CelluleInfo) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hashCode = (Content != null ? Content.GetHashCode() : 0);
            hashCode = (hashCode*397) ^ _IsReadOnly.GetHashCode();
            hashCode = (hashCode*397) ^ (int) _CelluleInfoType;
            hashCode = (hashCode*397) ^ (_ImageUri != null ? _ImageUri.GetHashCode() : 0);
            hashCode = (hashCode*397) ^ (_BadValue != null ? _BadValue.GetHashCode() : 0);
            return hashCode;
        }
    }

    public string Content { get; set; }

    private string _Background;
    public string Background
    {
        get { return this._Background; }
        set { this._Background = value; }
    }

    private string _Foreground;
    public string Foreground
    {
        get { return this._Foreground; }
        set { this._Foreground = value; }
    }

    private int _FontSize = 12;
    public int FontSize
    {
        get { return this._FontSize; }
        set { this._FontSize = value; }
    }

    private FontWeight _FontWeight = FontWeights.Normal;
    public FontWeight FontWeight
    {
        get { return this._FontWeight; }
        set { this._FontWeight = value; }
    }

    private FontStyle _FontStyle = FontStyles.Normal;
    public FontStyle FontStyle
    {
        get { return this._FontStyle; }
        set { this._FontStyle = value; }
    }

    private string _FontFamily;
    public string FontFamily
    {
        get { return this._FontFamily; }
        set { this._FontFamily = value; }
    }

    private bool _IsUnderlined;
    public bool IsUnderlined
    {
        get { return this._IsUnderlined; }
        set { this._IsUnderlined = value; }
    }

    private bool _IsReadOnly = true;
    public bool IsReadOnly
    {
        get { return this._IsReadOnly; }
        set { this._IsReadOnly = value; }
    }

    private CelluleInfoType _CelluleInfoType;
    public CelluleInfoType CelluleInfoType
    {
        get { return this._CelluleInfoType; }
        set { this._CelluleInfoType = value; }
    }

    private bool? _BooleanValue;
    public bool? BooleanValue
    {
        get { return this._BooleanValue; }
        set { this._BooleanValue = value; }
    }

    private string _ImageUri;
    public string ImageUri
    {
        get { return this._ImageUri; }
        set { this._ImageUri = value; }
    }

    private string _BadValue;
    public string BadValue
    {
        get { return this._BadValue; }
        set { this._BadValue = value; }
    }

    public override string ToString()
    {
        return string.Format("Content: {0}, IsReadOnly: {1}, CelluleInfoType: {2}", Content, IsReadOnly, CelluleInfoType);
    }
}

public enum CelluleInfoType
{
    Texte = 0,
    Entier = 1,
    Decimal = 2,
    Date = 3,
    Boolean = 4,
    Enumeration = 5,
    Couleur = 6,
    Image = 7
}

public class ColumnInfo
{
    private string _Name;
    public string Name
    {
        get { return this._Name; }
        set { this._Name = value; }
    }

    private int _FontSize = 12;
    public int FontSize
    {
        get { return this._FontSize; }
        set { this._FontSize = value; }
    }

    private FontWeight _FontWeight = FontWeights.Black;
    public FontWeight FontWeight
    {
        get { return this._FontWeight; }
        set { this._FontWeight = value; }
    }

    private FontStyle _FontStyle = FontStyles.Normal;
    public FontStyle FontStyle
    {
        get { return this._FontStyle; }
        set { this._FontStyle = value; }
    }

    private string _FontFamily;
    public string FontFamily
    {
        get { return this._FontFamily; }
        set { this._FontFamily = value; }
    }

    private string _Background;
    public string Background
    {
        get { return this._Background; }
        set { this._Background = value; }
    }

    private string _Foreground;
    public string Foreground
    {
        get { return this._Foreground; }
        set { this._Foreground = value; }
    }

    private bool _IsUnderlined;
    public bool IsUnderlined
    {
        get { return this._IsUnderlined; }
        set { this._IsUnderlined = value; }
    }

    private int _Width;
    public int Width
    {
        get { return this._Width; }
        set { this._Width = value; }
    }

    private bool _ValidationAndMoveDownInContinue;
    public bool ValidationAndMoveDownInContinue
    {
        get { return this._ValidationAndMoveDownInContinue; }
        set { this._ValidationAndMoveDownInContinue = value; }
    }

    private string _Tag;
    public string Tag
    {
        get { return this._Tag; }
        set { this._Tag = value; }
    }

    private List<string> _Enumeration;
    public List<string> Enumeration
    {
        get { return this._Enumeration; }
        set { this._Enumeration = value; }
    }

    private string _InfoTextForText = "Text";
    public string InfoTextForText
    {
        get { return this._InfoTextForText; }
        set { this._InfoTextForText = value; }
    }

    private string _InfoTextForInteger = "Integer";
    public string InfoTextForInteger
    {
        get { return this._InfoTextForInteger; }
        set { this._InfoTextForInteger = value; }
    }

    private string _InfoTextForDouble = "Decimal";
    public string InfoTextForDouble
    {
        get { return this._InfoTextForDouble; }
        set { this._InfoTextForDouble = value; }
    }

    private string _InfoTextForDate = "Date";
    public string InfoTextForDate
    {
        get { return this._InfoTextForDate; }
        set { this._InfoTextForDate = value; }
    }
}

 

Enfin, quelques helpers :

internal sealed class DcSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    private DcSafeHandle() : base(true) { }

    protected override Boolean ReleaseHandle()
    {
        return UnsafeNativeMethods.DeleteDC(base.handle);
    }
}

public class DeviceHelper
{
    public static Int32 PixelsPerInch(Orientation orientation)
    {
        var capIndex = (orientation == Orientation.Horizontal) ? 0x58 : 90;
        using (var handle = UnsafeNativeMethods.CreateDC("DISPLAY"))
        {
            return (handle.IsInvalid ? 0x60 : UnsafeNativeMethods.GetDeviceCaps(handle, capIndex));
        }
    }
}

 

Ce composant peut aisément être enrichi, et pallier certains cas de figure où un datagrid ne convient pas.

En conclusion

Bien que le composant supporte l’affichage de centaines de colonnes et de milliers de lignes sans problème, il faut également garder à l’esprit qu’il est souvent nécessaire de paginer ses données – visualiser toutes les données en simultanée n’est généralement pas très pertinent -, voire même de repenser l’ergonomie de l’écran.

Vous trouverez ici un exemple de code source à télécharger !

Share
Patrick LEGRAND
Patrick LEGRAND

1746

Leave a Reply

Your email address will not be published. Required fields are marked *