2015-12-08 96 views
8

Ho un DataGridView con colonne "Tipo" e "Valore". L'utente seleziona un tipo di dati, quindi inserisce un valore compatibile con quel tipo, alcuni tipi (ad esempio "Testo") accettano qualsiasi stringa. Altri tipi (ad es. "Sì/No") sono limitati a un elenco di valori possibili. Per ciascuna riga, imposto la cella nella colonna "Valore" in modo che sia una casella di testo per i tipi di forma libera o una casella combinata per i tipi di elenco. DataGridView è associato a un DataTable.Vuota cella Datagridview con dataset associato

Il problema si verifica se l'utente immette un valore per un tipo, ma poi passa la riga a un tipo diverso per il quale il valore corrente non è consentito. Non importa cosa cerco, non posso cancellare la cella Value. Quindi quando assegno una casella combinata alla cella, ottengo un'eccezione DataError, perché il valore corrente della cella non è compatibile. Come posso cancellare il valore prima di cambiare la cella da una casella di testo in una casella combinata?

public enum DataType 
{ 
    Text, 
    YesNo 
} 

public class IndexedItem 
{ 
    public string Name { get; set; } 
    public int ID {get; set; } 

    public string strID 
    { 
     get { return ID.ToString(); } 
    }  

    //constructors & other methods; 
} 

public static DataTable ParameterTable; 
public List<IndexedItem> YesNoList; 

Nel costruttore modulo (dgvInputs è DataGridView):

ParameterTable = new DataTable("ParameterTable"); 
ParameterTable.Columns.Add("Type", typeof(DataType)); 
ParameterTable.Columns.Add("Value", typeof(string)); 

YesNoList = new List<IndexedItem>(); 
YesNoList.Add(new IndexedItem("Yes", 1)); 
YesNoList.Add(new IndexedItem("No", 0)); 

var D = (DataGridViewComboBoxColumn)dgvInputs.Columns[0]; 
D.ValueMember = "Value"; 
D.DisplayMember = "Display"; 
D.DataSource = new DataType[] { 
     DataType.Text, 
     DataType.YesNo 
}.Select(x => new { Display = x.ToString(), Value = (int)x }).ToList(); 

BindingSource ParamSource = new BindingSource(); 
ParamSource.DataSource = ParameterTable; 
dgvInputs.AutoGenerateColumns = false; 
dgvInputs.DataSource = ParamSource; 
dgvInputs.Columns[0].DataPropertyName = "Type"; 
dgvInputs.Columns[1].DataPropertyName = "Value"; 

ed eventi:

private void dgvInputs_CurrentCellDirtyStateChanged(object sender, EventArgs e) { 
    if (dgvInputs.IsCurrentCellDirty) { 
     dgvInputs.CommitEdit(DataGridViewDataErrorContexts.Commit); 
    } 
} 

private void dgvInputs_CellValueChanged(object sender, DataGridViewCellEventArgs e) { 
    if (e.RowIndex >= 0 && e.ColumnIndex == 0) { 
     var cb = (DataGridViewComboBoxCell)dgvInputs[0, e.RowIndex]; 
     if (cb.Value != null && cb.Value != DBNull.Value) { 
     DataType Type = (DataType)cb.Value; 
     dgvInputs[1, e.RowIndex].Value = string.Empty; 
     dgvInputs.CommitEdit(DataGridViewDataErrorContexts.Commit); 
     switch (Type) { 
      case DataType.YesNo: 
       dgvInputs[1, e.RowIndex].Dispose(); 
       var newBox = new DataGridViewComboBoxCell(); 
       newBox.DisplayMember = "Name"; 
       newBox.ValueMember = "strID"; 
       newBox.DataSource = YesNoList; 
       dgvInputs[1, e.RowIndex] = newBox; 
       break; 
      default: 
       dgvInputs[1, e.RowIndex] = new DataGridViewTextBoxCell(); 
       break; 
     } 
     } 
    } 
} 

Se lo avete impostato su "testo" e immettere qualcosa di arbitrario, allora passa a "YesNo", restituisce un errore "System.ArgumentException: DataGridViewComboBoxCell valore non valido.", che riapparirà ogni volta che il cursore si trova sopra la cella. La modifica di una riga di testo fa riapparire il valore originale.

Suppongo che il problema è che il valore viene salvato in ParameterTable, ma non riesco a farlo propagare la cancellazione del valore originale in ParameterTable. Ho provato null e DBNull.Value invece di string.Empty, ma nessuno dei due ha fatto alcuna differenza. Ho aggiunto la riga "CommitEdit" nella speranza di ottenere il cambiamento, ma non ha fatto nessuna differenza.

Edit: Come si è visto, il problema era questo codice che ho avuto in caso di cambiamento di cella:

string Default = dgvInputs[4, e.RowIndex].Value as string; 
// code switching out text box and combo box above 
try 
{ 
    dgvInputs[4, e.RowIndex].Value = Default; 
} catch (Exception e2) { 
    MessageBox.Show(e2.GetType().ToString()); 
} 

L'idea era stata quella di preservare il valore, se possibile, e ho avuto la messagebox per mostrarmi l'eccezione specifica che dovevo prendere, perché non ne ero sicuro. Ma a quanto pare questo incarico non induce immediatamente l'eccezione. Ciò accade solo più tardi, apparentemente durante un altro evento che non sto trattando.

È evidente a posteriori che avrei dovuto includere questo codice nel campione. Non ho idea di come l'ho trascurato. Le mie scuse a tutti ho guidato un inseguimento selvaggio dell'oca lasciando fuori le informazioni critiche. Apprezzo tutta la tua assistenza.

+0

Tipo DBType = (DataType) cb.Value; non si compila –

+0

Ho copiato il codice, corretto alcuni errori di compilazione, eseguito, selezionare entrambi i tipi e immettere o selezionare i valori, non ha fatto apparire alcun messaggio di errore. –

+0

@LeiYang - Ho severamente troncato il codice per l'esempio e ho provato a cambiare "DBType" in "DataType" perché c'è un "DbType" in una libreria di riserva (che ho scoperto solo dopo aver scelto il mio nome) e non l'ho fatto Voglio confonderci. Dovrò provare l'esempio troncato qui me stesso. Anche se ho provato a non farlo, potrei aver rimosso qualcosa di rilevante. –

risposta

1

Il problema non è con la cancellazione del valore ma con YesNoList.

Il compbobox della griglia tenta di trovare il valore per il record corrente e non vi è alcun valore nullo né vuoto in YesNoList.

Si otterrà persino un errore se si tenta di aggiungere un nuovo record e prima di impostare DataType senza impostare il valore.

È possibile risolvere questo problema aggiungendo un elemento vuoto a SìNoList o impostando un valore predefinito sul record esistente quando si passa DataType.

+0

Grazie per la risposta, ma perché non genera un errore se prima scelgo una casella SìNo, anche se non sono stati immessi dati nel campo? O se non assegno nulla alla casella di testo prima di passare alla casella combinata? Se questa fosse la causa, dovrei avere errori anche in questi casi. –

+0

Ciò accade ogni volta che viene aggiunta una nuova riga. La griglia verrà visualizzata nell'elenco a discesa per trovare il valore corrente della cella. Nel caso in cui la nuova riga provenga dal menu a tendina c'è già un valore dalla lista, se la nuova riga proviene da un'altra cella, allora il valore è qualunque sia il valore predefinito della colonna nel datatable impostato (nel tuo caso null). il valore nullo non è presente nell'elenco a discesa e viene visualizzato un errore. –

+0

Scusa, ma non è quello che sto vedendo. Non ottengo alcun errore quando aggiungo una nuova riga da una colonna diversa anche se la casella combinata è nullo.Non ho alcun errore se la casella di testo era vuota prima di passare al tipo per far apparire la casella combinata. anche se la casella combinata è nullo quando appare. Ricevo solo un errore se la casella di testo contiene del testo prima dell'interruttore. Inoltre, come notato da Lei Yang, il codice semplificato che ho fornito qui funziona davvero, cosa che non farebbe se questo fosse il problema. Dovrò capire cosa ho asportato che non avrei dovuto avere quando ho creato il post. –

0

Modifica: So che di seguito non rispondo in modo specifico alla domanda come indicato, ma l'esempio può essere d'aiuto. Prendi in considerazione due controller in una cella.


originale

Io non sono sicuro se questo vi aiuterà o no, ma ho cercato di fare un programma molto semplice che avete discusso. Un set di dati viene creato con 2 voci. La prima colonna è DataType, la seconda è Value. Se viene scelto il Testo DataType, la cella Value diventa una Textbox. Se si sceglie Sì/No DataType, nasconde la casella di testo e mostra una DropDownList. L'idea è di nascondere un componente quando non è necessario.

Default.aspx.cs

using System; 
using System.Collections.Generic; 
using System.Data; 
using System.Linq; 
using System.Web; 
using System.Web.UI; 
using System.Web.UI.WebControls; 


namespace DataGridViewBounds 
{ 
    public partial class _Default : Page 
    { 
    public enum DataType 
    { 
     Text, 
     YesNo 
    } 

    public class IndexedItem 
    { 
     public string Name 
     { get; set; } 
     public int ID 
     { get; set; } 
     public string strID 
     { get { return ID.ToString(); } } 
    } 

    protected void Page_Load (object sender, EventArgs e) 
    { 

     if (!IsPostBack) 
     { 
     Bind(); 
     } 

     for (int i = 0; i < dg.Items.Count; ++i) 
     { 
     bool ShowText = ((DropDownList)dg.Items[i].Cells[0].Controls[1]).SelectedValue.Equals("text"); 
     ((DropDownList)dg.Items[i].Cells[1].Controls[1]).Visible = !ShowText; 
     ((TextBox)dg.Items[i].Cells[1].Controls[3]).Visible = ShowText; 
     } 

    } 

    private void Bind() 
    { 
     DataTable ParameterTable = new DataTable("ParameterTable"); 
     ParameterTable.Columns.Add("", typeof(string)); 
     ParameterTable.Columns.Add("Type", typeof(DataType)); 
     ParameterTable.Columns.Add("Value", typeof(string)); 

     List<ListItem> YesNoList = new List<ListItem>(); // Should be ListItem, not IndexedItem 
     YesNoList.Add(new ListItem("Yes", "1")); 
     YesNoList.Add(new ListItem("No", "0")); 

     DataRow row = ParameterTable.NewRow(); 
     row["Type"] = DataType.Text; 
     row["Value"] = "Some text"; 

     DataRow row2 = ParameterTable.NewRow(); 
     ParameterTable.Rows.Add(row); 
     row2["Type"] = DataType.YesNo; 
     row2["Value"] = "false"; 
     ParameterTable.Rows.Add(row2); 

     dg.DataSource = ParameterTable; 
     dg.DataBind(); 
     dg.ShowHeader = true; 
     dg.Visible = true; 

     for (int i = 0; i < dg.Items.Count; ++i) 
     { // Showing 2 ways to bind the DropDownList items 
     ((DropDownList)dg.Items[i].Cells[0].Controls[1]).Items.Add(new ListItem("Text", "text")); 
     ((DropDownList)dg.Items[i].Cells[0].Controls[1]).Items.Add(new ListItem("Yes/No", "bool")); 

     ((DropDownList)dg.Items[i].Cells[1].Controls[1]).DataSource = YesNoList; 
     ((DropDownList)dg.Items[i].Cells[1].Controls[1]).DataBind(); 
     } 
    } 
    } 
} 

E la pagina Default.aspx

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="DataGridViewBounds._Default" %> 

<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server"> 
<asp:DataGrid ID="dg" runat="server" AutoGenerateColumns="false"> 
<Columns> 
    <asp:TemplateColumn HeaderText="Type"> 
    <ItemTemplate> 
    <asp:DropDownList runat="server" ID="ddListType" AutoPostBack="true"></asp:DropDownList> 
    <asp:Label id="TypeLabel" runat="server" Visible="false"></asp:Label>  
    </ItemTemplate> 
    </asp:TemplateColumn> 

    <asp:TemplateColumn HeaderText="Value"> 
    <ItemTemplate> 
    <asp:DropDownList runat="server" ID="ddListValue" AutoPostBack="true" Visible="false"></asp:DropDownList> 
    <asp:TextBox id="ValueLabel" runat="server" Visible="false"></asp:TextBox> 
    </ItemTemplate> 
    </asp:TemplateColumn> 

</Columns> 
</asp:DataGrid> 
</asp:Content> 

Questo è il meglio che potessi fare al momento senza vedere più del codice, ma si può può usalo Un suggerimento, dgvInputs_CurrentCellDirtyStateChanged sembra impegnare il codice. Sto assumendo che questo è il codice SQL. Potrebbe essere necessario attendere fino a quando non viene premuto un pulsante "Invia" finale o "Accetta modifiche" in modo da non dover chiamare SQL così tanto, ma anche se si verifica un errore tra l'inizio del prima chiamata SQL e l'ultima. Se si verifica un'interruzione tra i due, potresti non voler necessariamente eseguire il commit.

+0

Questa è un'applicazione WinForms, non una pagina Web, ma esaminerò se l'idea è traducibile. Forse può essere fatto in WPF, ma sono a malapena più che consapevole dell'esistenza di WPF e preferisco evitare di dover imparare un nuovo paradigma per gestirlo se posso. –

+0

Non c'è SQL, solo il datatable. Il "commit" era parte del codice che ho preso in prestito da altri post (sia in SO che in altri forum, non ricordo) su come avere caselle di testo e caselle combinate nella stessa colonna. Non sono sicuro del motivo per cui era lì. –

+0

Ora che ho un campione funzionante da sperimentare (grazie ai commenti di Lei Yang sull'OP), ho scoperto che il punto dell'evento di commit di DirtyCell è che l'opzione textbox/combobox si verifica non appena il tipo viene modificato. Senza questo evento, non si verifica fino a quando la cella Tipo perde l'attenzione. –