0

I found a problem with binding while making a simple color picker. After binding each cell to the indexer in Datagrid, if you click a specific cell 2 times in a row, the binding is removed. The images below are shown the state of Textblock's Visibility. The initial visibility is 'collapsed', but after the second click the visibility is changed to 'visible' as the binding is removed. It might be related to the selection of the row or cell, but I cannot find any clues.

Initial view.

Init state

After the first click the cell 0,0.

Cell(0,0) is clicked

After the second click the cell 0,0.

The binding for the cell is removed after the second click.

Cell(0,0) is clicked again

<UserControl 
    x:Class="PracticeCode.TestColorPicker"
    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:local="clr-namespace:PracticeCode"
    mc:Ignorable="d" 
    d:DesignHeight="450" d:DesignWidth="800"
    Loaded="UserControl_Loaded">
    <UserControl.Resources>
        <DataTemplate x:Key="ColorCell">
            <Grid Background="Red">
                <Rectangle Panel.ZIndex="1" Width="30" Height="30"></Rectangle>
                <TextBlock Panel.ZIndex="2" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="Black" FontWeight="Bold">v</TextBlock>
            </Grid>
        </DataTemplate>
    </UserControl.Resources>
    <Grid>
        <DataGrid  
            Name="dgColorPicker" 
            HeadersVisibility="None"  
            GridLinesVisibility="None" 
            AutoGenerateColumns="False">
        </DataGrid>
    </Grid>
</UserControl>
public partial class TestColorPicker : UserControl
{
    public TestColorPicker()
    {
        InitializeComponent();
    }

    public List<ColorInfo> ColorInfoList { get; set; }


    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        ColorInfoList = new List<ColorInfo>()
        {
            new ColorInfo(0),
            new ColorInfo(1),
        };

        dgColorPicker.ItemsSource = ColorInfoList;

        int colCnt = ColorInfoList.Max(info => info.ColCount);
        var rowCnt = dgColorPicker.Items.Count;

        for (int colIndex = 0; colIndex < colCnt; colIndex++)
        {
            DataGridTemplateColumn templateColumn = new DataGridTemplateColumn();
            templateColumn.CellTemplate = (DataTemplate)Resources["ColorCell"];
            dgColorPicker.Columns.Add(templateColumn);

            for (int rowIndex = 0; rowIndex < rowCnt; rowIndex++)
            {
                var dgCell = UIHelper.GetDataGridCell(dgColorPicker, rowIndex, colIndex);
                var textblock = UIHelper.GetVisualChild<TextBlock>(dgCell);
                //
                // Binding
                //
                Binding binding = new Binding();
                binding.Path = new PropertyPath(string.Format("ColorCheckes[{0}]", colIndex));
                binding.Source = ColorInfoList[rowIndex];
                binding.Mode = BindingMode.TwoWay;
                binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
                BindingOperations.SetBinding(textblock, TextBlock.VisibilityProperty, binding);
                //
                // Events
                //
                dgCell.PreviewMouseLeftButtonDown += DgCell_MouseLeftButtonDown;
            }
        }

        dgColorPicker.UpdateLayout();
    }

    private void DgCell_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        // --------------------------------------------
        // Check if the binding is removed.
        // --------------------------------------------
        var textblock = UIHelper.GetVisualChild<TextBlock>(UIHelper.GetDataGridCell(dgColorPicker, 0, 0));
        BindingExpression be = BindingOperations.GetBindingExpression(textblock, TextBlock.VisibilityProperty);

        Console.WriteLine(be == null ? "Binding is removed" : "Alive");
    }
}
public class ColorInfo : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }
    }

    public Dictionary<int, Visibility> Visibilities = new Dictionary<int, Visibility>();

    public int ColCount => Visibilities.Count();

    [IndexerName("ColorCheckes")]
    public Visibility this[int colIndex]
    {
        get
        {
            return Visibilities.ContainsKey(colIndex) ? Visibilities[colIndex] : Visibility.Collapsed;
        }
        set
        {
            if (Visibilities.ContainsKey(colIndex)) { Visibilities[colIndex] = value; }
            else { Visibilities.Add(colIndex, value); }

            NotifyPropertyChanged();
        }
    }

    public ColorInfo(int rowIndex)
    {
        this[0] = Visibility.Collapsed;
        this[1] = Visibility.Collapsed;
    }
}
public static class UIHelper
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="parent"></param>
    /// <returns></returns>
    public static T GetVisualChild<T>(Visual parent) where T : Visual
    {
        T child = default(T);
        int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < numVisuals; i++)
        {
            Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
            child = v as T;

            if (v == null) { continue; }
            else if (child == null) { child = GetVisualChild<T>(v); }
            else if (child != null) { break; }
        }
        return child;
    }

    /// <summary>
    /// https://www.aspforums.net/Threads/517789/Get-Cell-Value-of-DataGrid-using-Loop-in-WPF/
    /// </summary>
    /// <param name="row"></param>
    /// <param name="column"></param>
    /// <returns></returns>
    public static DataGridCell GetDataGridCell(DataGrid dg, int row, int column)
    {
        DataGridRow rowData = GetRow(dg, row);

        if (rowData != null)
        {
            DataGridCellsPresenter cellPresenter = GetVisualChild<DataGridCellsPresenter>(rowData);
            DataGridCell cell = (DataGridCell)cellPresenter.ItemContainerGenerator.ContainerFromIndex(column);
            if (cell == null)
            {
                dg.ScrollIntoView(rowData, dg.Columns[column]);
                cell = (DataGridCell)cellPresenter.ItemContainerGenerator.ContainerFromIndex(column);
            }
            return cell;
        }
        return null;
    }

    public static DataGridRow GetRow(DataGrid dg, int index)
    {
        DataGridRow row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(index);
        if (row == null)
        {
            dg.UpdateLayout();
            dg.ScrollIntoView(dg.Items[index]);
            row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(index);
        }
        return row;
    }
}
  • Does it have to be DataGrid? you can achieve same UI with ListBox and UniformGrid (see https://stackoverflow.com/questions/37145391/how-to-create-and-use-matrix-of-color-boxes-c-sharp-wpf, replace ItemsControl with ListBox to allow selection (ListBox has SelectedItem property)) – ASh Aug 08 '22 at 13:03
  • Please trim your code to make it easier to find your problem. Follow these guidelines to create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Community Aug 08 '22 at 19:31
  • That's a lot of code for a so-called "simple" color picker. I would probably take a step back and rethink your approach. As far as the data binding being removed, it would appear that UserControl_Loaded is the wrong place to set your bindings. They are likely being overwritten by the DataTemplate at some point. You should either define your bindings in the DataTemplate markup (preferred) or find a better place to set your bindings in code-behind if you must do it in code. – Harlow Burgess Aug 08 '22 at 20:27
  • @Ash Thank you for your advice. I will try the ListBox and UniformGrid. However, I'd like to understand the issue. Can you give me some more info? – ShinKee Cho Aug 09 '22 at 03:23
  • @HarlowBurgess Thank you for your answer. I agree with your advice about rethinking and finding more simple approach. However, the problem itself is really curious. I've already set and test the binding code in other place, but the result was same. Interesting thing is that it will work very well if you do not click a cell twice in a row. – ShinKee Cho Aug 09 '22 at 04:12
  • I solved this issue by disable row selection. Please refer to https://stackoverflow.com/a/3050650/10622366. – ShinKee Cho Aug 09 '22 at 04:49

0 Answers0