Como tornar o WPF DataGridCell somente leitura?
-
27-09-2019 - |
Pergunta
Entendo que você pode tornar o DataGrid inteiro ou uma coluna inteira somente pronto (IsReadOnly = true).No entanto, no nível da célula, esta propriedade está apenas pronta.Mas eu preciso desse nível de granularidade.Há um blog sobre como adicionar IsReadOnly a uma linha alterando o código-fonte nos velhos tempos, quando o DataGrid era de domínio público, mas agora não tenho o código-fonte do DataGrid.Qual é a solução alternativa?
Desativar a célula (IsEnabled = false) quase atende à minha necessidade.Mas o problema é que você não consegue nem clicar na célula desativada para selecionar a linha (tenho o modo de seleção de linha completa).
EDITAR:Como ninguém respondeu a essa pergunta, acho que não é uma solução fácil.Aqui está uma solução possível:Torne a célula não editável.O único problema é que clicar na célula não seleciona a linha.Acabei de notar que o evento MouseDown ou MouseUp do DataGrid ainda é acionado quando a célula desabilitada é clicada.Neste manipulador de eventos, se eu conseguisse descobrir a linha em que ele clicou, poderia selecionar a linha programaticamente.No entanto, não consegui descobrir como encontrar a linha subjacente de DataGrid.InputHitTest
.Alguém pode me dar alguma dica?
Solução
Eu encontrei o mesmo problema, a célula deve ser somente leitura em algumas linhas, mas não nos outros. Aqui está uma solução alternativa:
A idéia é mudar dinamicamente o CellEditingTemplate
Entre dois modelos, um é o mesmo que o no CellTemplate
, o outro é para edição. Isso faz com que o modo de edição age exatamente o mesmo que a célula sem edição, embora esteja no modo de edição.
A seguir, é apresentado algum código de amostra para fazer isso, observe que essa abordagem requer DataGridTemplateColumn
:
Primeiro, defina dois modelos para células somente leitura e edição:
<DataGrid>
<DataGrid.Resources>
<!-- the non-editing cell -->
<DataTemplate x:Key="ReadonlyCellTemplate">
<TextBlock Text="{Binding MyCellValue}" />
</DataTemplate>
<!-- the editing cell -->
<DataTemplate x:Key="EditableCellTemplate">
<TextBox Text="{Binding MyCellValue}" />
</DataTemplate>
</DataGrid.Resources>
</DataGrid>
Em seguida, defina um modelo de dados com adicional ContentPresenter
camada e uso Trigger
Para mudar o ContentTemplate
do ContentPresenter
, então os dois modelos acima podem ser alterados dinamicamente pelo IsEditable
vinculativo:
<DataGridTemplateColumn CellTemplate="{StaticResource ReadonlyCellTemplate}">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<!-- the additional layer of content presenter -->
<ContentPresenter x:Name="Presenter" Content="{Binding}" ContentTemplate="{StaticResource ReadonlyCellTemplate}" />
<DataTemplate.Triggers>
<!-- dynamically switch the content template by IsEditable binding -->
<DataTrigger Binding="{Binding IsEditable}" Value="True">
<Setter TargetName="Presenter" Property="ContentTemplate" Value="{StaticResource EditableCellTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
Hth
Outras dicas
Depois de muita pesquisa e experimentação usando o ISTABSTOP = FALSE e FOCUSLABLE = FALSE FUNCIONA MELHORES PARA MIM.
<DataGridTextColumn Header="My Column" Binding="{Binding Path=MyColumnValue}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ReadOnly}" Value="True">
<Setter Property="IsTabStop" Value="False"></Setter>
<Setter Property="Focusable" Value="False"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
Existe uma propriedade em DataGridCell.IsReadOnly
que você pode pensar que pode se ligar,
por exemplo, usando xaml como este:
<!-- Won't work -->
<DataGrid Name="myDataGrid" ItemsSource="{Binding MyItems}">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<Setter Property="IsReadOnly" Value="{Binding MyIsReadOnly}" />
</Style>
</DataGrid.Resources>
<!-- Column definitions... -->
</DataGrid>
Infelizmente, isso não funcionará porque essa propriedade não é gravada.
Em seguida, você pode tentar interceptar e interromper os eventos do mouse, mas isso não impedirá que o usuário digite o modo de edição usando a tecla F2.
A maneira como eu abriu isso foi ouvindo o PreviewExecutedEvent
no datagrid e, em seguida, sinalizando -o condicionalmente como tratado.
Por exemplo, adicionando código semelhante a esse construtor da minha janela ou UserControl (ou outro local mais adequado):
myDataGrid.AddHandler(CommandManager.PreviewExecutedEvent,
(ExecutedRoutedEventHandler)((sender, args) =>
{
if (args.Command == DataGrid.BeginEditCommand)
{
DataGrid dataGrid = (DataGrid) sender;
DependencyObject focusScope = FocusManager.GetFocusScope(dataGrid);
FrameworkElement focusedElement = (FrameworkElement) FocusManager.GetFocusedElement(focusScope);
MyRowItemModel model = (MyRowItemModel) focusedElement.DataContext;
if (model.MyIsReadOnly)
{
args.Handled = true;
}
}
}));
Ao fazer assim, as células ainda são focadas e selecionáveis.
Mas o usuário não poderá inserir o modo de edição, a menos que seus itens de modelo permitam a linha fornecida.
E você não sofrerá os custos ou complexidades de desempenho usando o DataGridTemplateColumn.
Eu resolvi esse problema em meu aplicativo, configurando o objeto subjacente na célula (por exemplo, caixa de seleção) - ishittestVisible = false; Foco = false;
var cb = this.dataGrid.Columns[1].GetCellContent(row) as CheckBox;
cb.IsHitTestVisible = false;
cb.Focusable = false;
"Row" é um datagridrow. IshittestVisible = false, significa que você não pode clicar/selecionar/manipular o objeto subjacente via mouse, mas ainda pode selecionar o DataGridCell. Focusable = false, significa que você não pode selecionar/manipular o objeto subjacente com o teclado. Isso dá a ilusão de uma célula leitura, mas você ainda pode selecionar a célula e tenho certeza SelectionMode = Fullrow Em seguida, clicar na célula "Lead Somente" selecionará a linha inteira.
Minha solução é usar a ligação ao datagridTemplatecolumn com o conversor.
<UserControl.Resources>
<c:isReadOnlyConverter x:Key="isRead"/>
</UserControl.Resources>
<DataGridTemplateColumn x:Name="exampleTemplate" Header="example:" Width="120" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="exampleCheckBox" VerticalAlignment="Center" IsEnabled="{Binding ElementName=exmpleTemplate, Path=IsReadOnly, Converter={StaticResource isRead}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
e o conversor:
class isReadOnlyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
return !(bool)value;
}
catch (Exception)
{
return false;
}
}
Com base no comentário @Sohum, aqui você pode usar a versão simplificada da resposta marcada como resposta.
dataGrid.BeginningEdit += DataGrid_BeginningEdit;
(...)
private static void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
//Actual content of the DataGridCell
FrameworkElement content = e.Column.GetCellContent(e.Row);
MyObject myObject = (MyObject)content.DataContext;
if (!myObject.CanEdit)
{
e.Cancel = true;
}
}
Você pode usá -lo mais tarde como comportamento da propriedade anexada.
Uma maneira de obter células de texto selecionáveis e somente leitura para DataGrid é usar modelo e estilo como este:
<DataGrid>
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border Padding="0" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<TextBox BorderThickness="0" MouseDoubleClick="DataGrid_TextBox_MouseDoubleClick" IsReadOnly="True" Padding="5" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Text}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.CellStyle>
E para back-end CS:
private void DataGrid_TextBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
(sender as TextBox).SelectAll();
}
Isso é um pouco tarde, mas eu também estava investigando isso, essas soluções funcionam bem, mas eu precisava de algo um pouco diferente, fiz o seguinte e funciona exatamente como eu queria e o que a pergunta está procurando.
Essencialmente, eu queria entrar no modo de edição para a célula e ter todos esses outros modelos e comandar a lógica da mesma forma, sem conseguir editar a célula.
A solução para tudo isso é definir a propriedade TextBox.
<Style TargetType="DataGridCell">
<Setter Property="TextBox.IsReadOnly" Value="True"/>
<EventSetter Event="PreviewKeyDown" Handler="cell_PreviewKeyDown"/>
</Style>
e o seguinte código para trás para interromper a edição inicial
protected void cell_PreviewKeyDown(object sender, KeyEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (cell.IsEditing == false &&
((Keyboard.Modifiers & ModifierKeys.Control) != ModifierKeys.Control)) //So that Ctrl+C keeps working
{
cell.IsEditing = true;
e.Handled = true;
}
}
Espero que isso seja útil.
No meu caso, eu estava usando DataGridTextColumn. Defino a propriedade IseNabled no ContentPresenter em estilo da seguinte maneira e funciona bem -
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Background="{TemplateBinding Background}" >
<ContentPresenter IsEnabled="{Binding Path=IsEditable}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
<DataGridTextColumn Header="A"
Binding="{Binding Path=A}"/>
</DataGrid>
Você pode fazer isso com um modelo de dados mais simples.
<DataGrid.Resources>
<DataTemplate x:Key="MyTemplate" DataType="MyRowDataType">
<TextBox Text="{Binding Value}" IsReadOnly="{Binding IsReadOnly}" />
</DataTemplate>
</DataGrid.Resources>
...
<DataGridTemplateColumn CellTemplate="{StaticResource MyTemplate}" />