jmr
jmr

Reputation: 27

WPF MVVM popup with checkBox inside DataGrid no fire events

Here is my goal (simplified) : Create a userControl to display readable bitField.

Exemple with value 0x3: want to display option1 (bit 1): enable, option2 (bit 2):enable, option3 (bit 3): disable ...

userControl behavior: if I click on this control, it's open a popup (like combobox) with checkBoxes which allow you to enable optionX (which will change the value and displayed text).

This is the source code I use for the UserControl view

<UserControl x:Name="userControl" x:Class="MyProg.Views.BitField"
             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:MyProg.Views"
             mc:Ignorable="d"
             >

    <Grid x:Name="LayoutRoot"  Height="{Binding ActualHeight, ElementName=userControl, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Width="{Binding ActualWidth, ElementName=userControl, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <ToggleButton x:Name="TogglePopupButton" Background="Transparent" BorderBrush="Transparent" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"  >
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="auto" />
                </Grid.ColumnDefinitions>
                <Label Content="{Binding Path=Text}" Grid.Column="0" VerticalAlignment="Center" Margin="0,-5,0,-5" />
                <Path x:Name="Arrow" Grid.Column="1" Fill="Black"  VerticalAlignment="Center" Data="M0,0 L0,2 L4,6 L8,2 L8,0 L4,4 z" HorizontalAlignment="Right"/>
            </Grid>
        </ToggleButton>

        <Popup x:Name="ToggledPopup" StaysOpen="False" IsOpen="{Binding IsChecked, ElementName=TogglePopupButton, Mode=TwoWay}" Width="{Binding ActualWidth, ElementName=userControl, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"  Grid.Row="1">
            <Border Background="White" BorderThickness="1" BorderBrush="Black">
                <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="5"/>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="5"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>

                        <CheckBox Content="option 1" Grid.Column="0" />
                        <CheckBox Content="option 2" Grid.Column="2" />
                        <CheckBox Content="option 3" Grid.Column="4" />
                    </Grid>
                </ScrollViewer>
            </Border>
        </Popup>
    </Grid>

</UserControl>

The code behind for binding value

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using MyProg.ViewModels;

namespace MyProg.Views
{
    /// <summary>
    /// Interaction logic for BitField.xaml
    /// </summary>
    public partial class BitField : UserControl
    {
        internal vmBitField m_ViewModel;

        public BitField()
        {
            InitializeComponent();
            // Load the viewModel
            m_ViewModel = new vmBitField();
            LayoutRoot.DataContext = m_ViewModel;
            m_ViewModel.PropertyChanged += M_ViewModel_PropertyChanged;
        }

        private void M_ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Value")
            {
                if(IntValue != m_ViewModel?.Value)
                    IntValue = (int)m_ViewModel.Value;
            }
        }

        /// <summary>
        /// Gets or sets the value which is displayed
        /// </summary>
        public int IntValue
        {
            get { return (int)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value);}
        }

        /// <summary>
        /// Identified the Value dependency property
        /// </summary>
        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("IntValue", typeof(int), typeof(BitField), new PropertyMetadata(0, OnIntValueSet));

        private static void OnIntValueSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((BitField)d).m_ViewModel.Value = (uint?)(int)e.NewValue;
        }
    }
}

and the UserControl viewModel

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MyProg.MVVM;

namespace MyProg.ViewModels
{
    class vmBitField : ViewModelBase
    {
        #region Members
        protected uint m_Value = 0; // 7
        #endregion

        #region Properties
        public uint? Value { get { return m_Value; } set { if (value != m_Value) { m_Value = (uint)value; RaiseEventPropertyChanged("Option1"); RaiseEventPropertyChanged("Option2"); RaiseEventPropertyChanged("Option3"); RaiseEventPropertyChanged("Text"); } } }
        public bool Option1 { get {return (Value & 0x1) == 1; } set { if (value != Option1) { m_Value ^= 0x1; RaiseEventPropertyChanged("Option1"); RaiseEventPropertyChanged("Value"); RaiseEventPropertyChanged("Text"); } } }
        public bool Option2 { get { return (Value & 0x2) == 1; } set { if (value != Option2) { m_Value ^= 0x2; RaiseEventPropertyChanged("Option2"); RaiseEventPropertyChanged("Value"); RaiseEventPropertyChanged("Text"); } } }
        public bool Option3 { get { return (Value & 0x4) == 1; } set { if (value != Option3) { m_Value ^= 0x4; RaiseEventPropertyChanged("Option3"); RaiseEventPropertyChanged("Value"); RaiseEventPropertyChanged("Text"); } } }
        public string Text { get { return "Option1:" + Option1 + " Option2:" + Option2 + " Option3:" + Option3; } }
        #endregion

        #region Contructors
        public vmBitField()
        {
        }
        #endregion

        #region Methodes
        #endregion
    }
}

This works correctly when I insert this userControl in a page or windows.

<local:LockType IntValue="{Binding Path=ValueAsInt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

Now, I have a list with lot of bitfield. I want to display all of this in a datagrid. So I want to insert this userControl in a dataGrid. This is the code to add the datagrid

<DataGrid Grid.Row="13"  Grid.ColumnSpan="3" AutoGenerateColumns="False" Name="dataGrid1" AlternationCount="2"
                  ItemsSource="{Binding Values}" 
                  CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="False" CanUserReorderColumns="False" >
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Id" HeaderStyle="{StaticResource DataGridHeaderCenter}" 
                                CellTemplate="{StaticResource IdText}"/>
        <DataGridTemplateColumn Header="Value" Width="400" HeaderStyle="{StaticResource DataGridHeaderCenter}" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <local1:LockType IntValue="{Binding Path=ValueAsInt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="CurrentValue" 
                                CellTemplate="{StaticResource CurrentValueText}" />
    </DataGrid.Columns>
</DataGrid>

When my usercontrol is in a datagrid, when I click on togglebuton, it's open the popup correctly, but then I can't interact (check or uncheck) with any of checkBoxes inside the popup.

Anyone could help me to find why checkboxes inside a popup doesn't fired event ? I could add more info if needed.

Best Regards JM

Upvotes: 0

Views: 854

Answers (1)

Raviraj Palvankar
Raviraj Palvankar

Reputation: 879

You will have to set the FocusManager.IsFocusScope of the popup to true. You can also set the cell's IsFocussable property through a style setter.

<Style TargetType="{x:Type DataGridCell}"> <Setter Property="Focusable" Value="False"></Setter> </Style>

The problem why it does not let you edit anything on the popup is because the cell editing has not ended. Hence, you can also do the following by registering to the DataGrid_CellEditEnding event.

private static void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
    if (e.Column.GetType() == typeof(DataGridTemplateColumn))
    {
        var popup = GetVisualChild<Popup>(e.EditingElement);
        if (popup != null && popup.IsOpen)
        {
            e.Cancel = true;
        }
    }   
}

Upvotes: 1

Related Questions