gui: convert data tables from TListView to TStringGrid
This commit is contained in:
parent
cecfc338d4
commit
d7e3363173
21
db_badger.go
21
db_badger.go
@ -42,25 +42,17 @@ func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
f.propertiesBox.SetText(content)
|
||||
|
||||
// Load data
|
||||
|
||||
f.contentBox.SetEnabled(false)
|
||||
f.contentBox.Clear()
|
||||
|
||||
// Badger always uses Key + Value as the columns
|
||||
|
||||
f.contentBox.Columns().Clear()
|
||||
colKey := f.contentBox.Columns().Add()
|
||||
colKey.SetCaption("Key")
|
||||
colKey.Title().SetCaption("Key")
|
||||
colKey.SetWidth(MY_WIDTH)
|
||||
colKey.SetAlignment(types.TaLeftJustify)
|
||||
colVal := f.contentBox.Columns().Add()
|
||||
colVal.SetCaption("Value")
|
||||
colVal.Title().SetCaption("Value")
|
||||
|
||||
err := ld.db.View(func(txn *badger.Txn) error {
|
||||
|
||||
// Valid
|
||||
f.contentBox.Clear()
|
||||
|
||||
// Create iterator
|
||||
opts := badger.DefaultIteratorOptions
|
||||
opts.PrefetchSize = 64
|
||||
@ -71,9 +63,10 @@ func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
item := it.Item()
|
||||
k := item.Key()
|
||||
err := item.Value(func(v []byte) error {
|
||||
dataEntry := f.contentBox.Items().Add()
|
||||
dataEntry.SetCaption(formatUtf8(k))
|
||||
dataEntry.SubItems().Add(formatUtf8(v))
|
||||
rpos := f.contentBox.RowCount()
|
||||
f.contentBox.SetRowCount(rpos + 1)
|
||||
f.contentBox.SetCells(0, rpos, formatUtf8(k))
|
||||
f.contentBox.SetCells(1, rpos, formatUtf8(v))
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -107,7 +100,7 @@ func (ld *badgerLoadedDatabase) NavContext(ndata *navData) ([]contextAction, err
|
||||
return nil, nil // No special actions are supported
|
||||
}
|
||||
|
||||
func (ld *badgerLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
|
||||
func (ld *badgerLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
|
||||
vcl.ShowMessage("Badger doesn't support querying")
|
||||
}
|
||||
|
||||
|
19
db_bolt.go
19
db_bolt.go
@ -48,19 +48,15 @@ func (ld *boltLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
f.propertiesBox.SetText(content)
|
||||
|
||||
// Load data
|
||||
|
||||
f.contentBox.SetEnabled(false)
|
||||
f.contentBox.Clear()
|
||||
|
||||
// Bolt always uses Key + Value as the columns
|
||||
|
||||
f.contentBox.Columns().Clear()
|
||||
colKey := f.contentBox.Columns().Add()
|
||||
colKey.SetCaption("Key")
|
||||
colKey.Title().SetCaption("Key")
|
||||
colKey.SetWidth(MY_WIDTH)
|
||||
colKey.SetAlignment(types.TaLeftJustify)
|
||||
colVal := f.contentBox.Columns().Add()
|
||||
colVal.SetCaption("Value")
|
||||
colVal.Title().SetCaption("Value")
|
||||
|
||||
err := ld.db.View(func(tx *bbolt.Tx) error {
|
||||
b := boltTargetBucket(tx, ndata.bucketPath)
|
||||
@ -70,13 +66,12 @@ func (ld *boltLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
}
|
||||
|
||||
// Valid
|
||||
f.contentBox.Clear()
|
||||
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
dataEntry := f.contentBox.Items().Add()
|
||||
dataEntry.SetCaption(formatUtf8(k))
|
||||
dataEntry.SubItems().Add(formatUtf8(v))
|
||||
rpos := f.contentBox.RowCount()
|
||||
f.contentBox.SetRowCount(rpos + 1)
|
||||
f.contentBox.SetCells(0, rpos, formatUtf8(k))
|
||||
f.contentBox.SetCells(1, rpos, formatUtf8(v))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@ -142,7 +137,7 @@ func (ld *boltLoadedDatabase) DeleteBucket(ndata *navData) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ld *boltLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
|
||||
func (ld *boltLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
|
||||
vcl.ShowMessage("Bolt doesn't support querying")
|
||||
}
|
||||
|
||||
|
@ -44,32 +44,25 @@ func (ld *debconfLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
|
||||
// Load data
|
||||
|
||||
f.contentBox.SetEnabled(false)
|
||||
f.contentBox.Clear()
|
||||
|
||||
// debconf always uses Key + Value as the columns
|
||||
|
||||
indexes := make(map[string]int)
|
||||
|
||||
f.contentBox.Columns().Clear()
|
||||
for i, cname := range ld.db.AllColumnNames {
|
||||
indexes[cname] = i
|
||||
|
||||
col := f.contentBox.Columns().Add()
|
||||
col.SetCaption(cname)
|
||||
col.Title().SetCaption(cname)
|
||||
col.SetWidth(MY_WIDTH)
|
||||
}
|
||||
|
||||
for _, entry := range ld.db.Entries {
|
||||
|
||||
cell := f.contentBox.Items().Add()
|
||||
cell.SetCaption(entry.Name)
|
||||
rpos := f.contentBox.RowCount()
|
||||
f.contentBox.SetRowCount(rpos + 1)
|
||||
f.contentBox.SetCells(0, rpos, entry.Name)
|
||||
|
||||
texts := make([]string, len(ld.db.AllColumnNames))
|
||||
for _, proppair := range entry.Properties {
|
||||
texts[indexes[proppair[0]]-1 /* compensate for 'Name' always being first */] = proppair[1]
|
||||
f.contentBox.SetCells(int32(indexes[proppair[0]]), rpos, proppair[1])
|
||||
}
|
||||
cell.SubItems().AddStrings2(texts)
|
||||
}
|
||||
|
||||
// Valid
|
||||
@ -91,7 +84,7 @@ func (ld *debconfLoadedDatabase) NavContext(ndata *navData) ([]contextAction, er
|
||||
return nil, nil // No special actions are supported
|
||||
}
|
||||
|
||||
func (ld *debconfLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
|
||||
func (ld *debconfLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
|
||||
vcl.ShowMessage("debconf doesn't support querying")
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,9 @@ func (n *noLoadedDatabase) RootElement() *vcl.TTreeNode {
|
||||
|
||||
func (n *noLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
f.propertiesBox.SetText("Open a database to get started...")
|
||||
f.contentBox.SetEnabled(false)
|
||||
f.contentBox.Clear()
|
||||
}
|
||||
|
||||
func (n *noLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
|
||||
func (n *noLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
|
||||
}
|
||||
|
||||
func (n *noLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
|
||||
|
18
db_pebble.go
18
db_pebble.go
@ -46,19 +46,14 @@ func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
f.propertiesBox.SetText(content)
|
||||
|
||||
// Load data
|
||||
|
||||
f.contentBox.SetEnabled(false)
|
||||
f.contentBox.Clear()
|
||||
|
||||
// pebble always uses Key + Value as the columns
|
||||
|
||||
f.contentBox.Columns().Clear()
|
||||
colKey := f.contentBox.Columns().Add()
|
||||
colKey.SetCaption("Key")
|
||||
colKey.Title().SetCaption("Key")
|
||||
colKey.SetWidth(MY_WIDTH)
|
||||
colKey.SetAlignment(types.TaLeftJustify)
|
||||
colVal := f.contentBox.Columns().Add()
|
||||
colVal.SetCaption("Value")
|
||||
colVal.Title().SetCaption("Value")
|
||||
|
||||
itr := ld.db.NewIterWithContext(ctx, nil)
|
||||
defer itr.Close()
|
||||
@ -71,9 +66,10 @@ func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
return
|
||||
}
|
||||
|
||||
dataEntry := f.contentBox.Items().Add()
|
||||
dataEntry.SetCaption(formatUtf8(k))
|
||||
dataEntry.SubItems().Add(formatUtf8(v))
|
||||
rpos := f.contentBox.RowCount()
|
||||
f.contentBox.SetRowCount(rpos + 1)
|
||||
f.contentBox.SetCells(0, rpos, formatUtf8(k))
|
||||
f.contentBox.SetCells(0, rpos, formatUtf8(v))
|
||||
}
|
||||
|
||||
// Valid
|
||||
@ -95,7 +91,7 @@ func (ld *pebbleLoadedDatabase) NavContext(ndata *navData) ([]contextAction, err
|
||||
return nil, nil // No special actions are supported
|
||||
}
|
||||
|
||||
func (ld *pebbleLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
|
||||
func (ld *pebbleLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
|
||||
vcl.ShowMessage("pebble doesn't support querying")
|
||||
}
|
||||
|
||||
|
42
db_redis.go
42
db_redis.go
@ -54,9 +54,7 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
|
||||
f.propertiesBox.SetText(infostr)
|
||||
|
||||
// Disable data tab
|
||||
f.contentBox.SetEnabled(false)
|
||||
f.contentBox.Clear()
|
||||
// Leave data tab disabled (default behaviour)
|
||||
|
||||
} else if len(ndata.bucketPath) == 1 {
|
||||
// One selected database
|
||||
@ -77,18 +75,14 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
|
||||
// Redis always uses Key + Value as the columns
|
||||
|
||||
f.contentBox.SetEnabled(false)
|
||||
f.contentBox.Clear()
|
||||
|
||||
f.contentBox.Columns().Clear()
|
||||
colKey := f.contentBox.Columns().Add()
|
||||
colKey.SetCaption("Key")
|
||||
colKey.Title().SetCaption("Key")
|
||||
colKey.SetWidth(MY_WIDTH)
|
||||
colKey.SetAlignment(types.TaLeftJustify)
|
||||
colType := f.contentBox.Columns().Add()
|
||||
colType.SetCaption("Type")
|
||||
colType.Title().SetCaption("Type")
|
||||
colVal := f.contentBox.Columns().Add()
|
||||
colVal.SetCaption("Value")
|
||||
colVal.Title().SetCaption("Value")
|
||||
|
||||
for _, key := range allKeys {
|
||||
typeName, err := ld.db.Type(ctx, key).Result()
|
||||
@ -97,9 +91,10 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
return
|
||||
}
|
||||
|
||||
dataEntry := f.contentBox.Items().Add()
|
||||
dataEntry.SetCaption(key) // formatUtf8
|
||||
dataEntry.SubItems().Add(typeName)
|
||||
rpos := f.contentBox.RowCount()
|
||||
f.contentBox.SetRowCount(rpos + 1)
|
||||
f.contentBox.SetCells(0, rpos, formatUtf8([]byte(key)))
|
||||
f.contentBox.SetCells(1, rpos, typeName)
|
||||
|
||||
switch typeName {
|
||||
case "string":
|
||||
@ -108,7 +103,7 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
vcl.ShowMessage(fmt.Sprintf("Loading %q/%q: %v", ndata.bucketPath[0], key, err))
|
||||
return
|
||||
}
|
||||
dataEntry.SubItems().Add(val)
|
||||
f.contentBox.SetCells(2, rpos, val)
|
||||
|
||||
case "hash":
|
||||
val, err := ld.db.HGetAll(ctx, key).Result()
|
||||
@ -117,7 +112,7 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
return
|
||||
}
|
||||
// It's a map[string]string
|
||||
dataEntry.SubItems().Add(formatAny(val))
|
||||
f.contentBox.SetCells(2, rpos, formatAny(val))
|
||||
|
||||
case "lists":
|
||||
fallthrough
|
||||
@ -129,7 +124,7 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
dataEntry.SubItems().Add("<<<other object type>>>")
|
||||
f.contentBox.SetCells(2, rpos, "<<<other object type>>>")
|
||||
}
|
||||
|
||||
}
|
||||
@ -170,7 +165,7 @@ func (ld *redisLoadedDatabase) NavContext(ndata *navData) ([]contextAction, erro
|
||||
return nil, nil // No special actions are supported
|
||||
}
|
||||
|
||||
func (ld *redisLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
|
||||
func (ld *redisLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Need to parse the query into separate string+args fields for the protocol
|
||||
@ -193,21 +188,24 @@ func (ld *redisLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView
|
||||
|
||||
resultArea.Columns().Clear()
|
||||
colVal := resultArea.Columns().Add()
|
||||
colVal.SetCaption("Result")
|
||||
colVal.Title().SetCaption("Result")
|
||||
|
||||
// The result is probably a single value or a string slice
|
||||
switch ret := ret.(type) {
|
||||
case []string:
|
||||
// Multiple values
|
||||
for _, single := range ret {
|
||||
cell := resultArea.Items().Add()
|
||||
cell.SetCaption(single) // formatUtf8
|
||||
|
||||
rpos := resultArea.RowCount()
|
||||
resultArea.SetRowCount(rpos + 1)
|
||||
resultArea.SetCells(0, rpos, formatUtf8([]byte(single)))
|
||||
}
|
||||
|
||||
default:
|
||||
// Single value
|
||||
dataEntry := resultArea.Items().Add()
|
||||
dataEntry.SetCaption(formatAny(ret)) // formatUtf8
|
||||
rpos := resultArea.RowCount()
|
||||
resultArea.SetRowCount(rpos + 1)
|
||||
resultArea.SetCells(0, rpos, formatAny(ret)) // formatUtf8
|
||||
|
||||
}
|
||||
|
||||
|
23
db_sqlite.go
23
db_sqlite.go
@ -42,14 +42,10 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
if len(ndata.bucketPath) == 0 {
|
||||
// Top-level
|
||||
f.propertiesBox.SetText("Please select...")
|
||||
f.contentBox.SetEnabled(false)
|
||||
f.contentBox.Clear()
|
||||
|
||||
} else if len(ndata.bucketPath) == 1 {
|
||||
// Category (tables, ...)
|
||||
f.propertiesBox.SetText("Please select...")
|
||||
f.contentBox.SetEnabled(false)
|
||||
f.contentBox.Clear()
|
||||
|
||||
} else if len(ndata.bucketPath) == 2 && ndata.bucketPath[0] == sqliteTablesCaption {
|
||||
// Render for specific table
|
||||
@ -66,9 +62,6 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
// Display table properties
|
||||
f.propertiesBox.SetText(fmt.Sprintf("Selected table %q\n\nSchema:\n\n%s", tableName, schemaStmt))
|
||||
|
||||
f.contentBox.SetEnabled(false)
|
||||
f.contentBox.Clear()
|
||||
|
||||
// Load column details
|
||||
// Use SELECT form instead of common PRAGMA table_info so we can just get names
|
||||
// We could possibly get this from the main data select, but this will
|
||||
@ -129,17 +122,17 @@ func (ld *sqliteLoadedDatabase) sqliteGetColumnNamesForTable(tableName string) (
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func populateColumns(names []string, dest *vcl.TListView) {
|
||||
func populateColumns(names []string, dest *vcl.TStringGrid) {
|
||||
dest.Columns().Clear()
|
||||
for _, columnName := range names {
|
||||
col := dest.Columns().Add()
|
||||
col.SetCaption(columnName)
|
||||
col.Title().SetCaption(columnName)
|
||||
col.SetWidth(MY_WIDTH)
|
||||
col.SetAlignment(types.TaLeftJustify)
|
||||
}
|
||||
}
|
||||
|
||||
func populateRows(rr *sql.Rows, dest *vcl.TListView) {
|
||||
func populateRows(rr *sql.Rows, dest *vcl.TStringGrid) {
|
||||
|
||||
numColumns := int(dest.Columns().Count())
|
||||
|
||||
@ -156,10 +149,10 @@ func populateRows(rr *sql.Rows, dest *vcl.TListView) {
|
||||
return
|
||||
}
|
||||
|
||||
dataEntry := dest.Items().Add()
|
||||
dataEntry.SetCaption(formatAny(fields[0]))
|
||||
for i := 1; i < len(fields); i += 1 {
|
||||
dataEntry.SubItems().Add(formatAny(fields[i]))
|
||||
rpos := dest.RowCount()
|
||||
dest.SetRowCount(rpos + 1)
|
||||
for i := 0; i < len(fields); i += 1 {
|
||||
dest.SetCells(int32(i), rpos, formatAny(fields[i]))
|
||||
}
|
||||
|
||||
}
|
||||
@ -169,7 +162,7 @@ func populateRows(rr *sql.Rows, dest *vcl.TListView) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ld *sqliteLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
|
||||
func (ld *sqliteLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
|
||||
rr, err := ld.db.Query(query)
|
||||
if err != nil {
|
||||
vcl.ShowMessage(err.Error())
|
||||
|
@ -19,7 +19,7 @@ type loadedDatabase interface {
|
||||
DriverName() string
|
||||
RootElement() *vcl.TTreeNode
|
||||
RenderForNav(f *TMainForm, ndata *navData)
|
||||
ExecQuery(query string, resultArea *vcl.TListView)
|
||||
ExecQuery(query string, resultArea *vcl.TStringGrid)
|
||||
NavChildren(ndata *navData) ([]string, error)
|
||||
NavContext(ndata *navData) ([]contextAction, error)
|
||||
Keepalive(ndata *navData)
|
||||
|
28
main.go
28
main.go
@ -30,9 +30,9 @@ type TMainForm struct {
|
||||
Buckets *vcl.TTreeView
|
||||
Tabs *vcl.TPageControl
|
||||
propertiesBox *vcl.TMemo
|
||||
contentBox *vcl.TListView
|
||||
contentBox *vcl.TStringGrid
|
||||
queryInput *vcl.TMemo
|
||||
queryResult *vcl.TListView
|
||||
queryResult *vcl.TStringGrid
|
||||
|
||||
none *noLoadedDatabase
|
||||
dbs []loadedDatabase
|
||||
@ -209,14 +209,12 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
|
||||
dataTab.SetCaption("Data")
|
||||
dataTab.SetImageIndex(imgTable)
|
||||
|
||||
f.contentBox = vcl.NewListView(dataTab)
|
||||
f.contentBox = vcl.NewStringGrid(dataTab)
|
||||
f.contentBox.SetParent(dataTab)
|
||||
f.contentBox.BorderSpacing().SetAround(MY_SPACING)
|
||||
f.contentBox.SetAlign(types.AlClient) // fill remaining space
|
||||
f.contentBox.SetViewStyle(types.VsReport) // "Report style" i.e. has columns
|
||||
f.contentBox.SetAutoWidthLastColumn(true)
|
||||
f.contentBox.SetReadOnly(true)
|
||||
f.contentBox.Columns().Clear()
|
||||
f.contentBox.SetAlign(types.AlClient) // fill remaining space
|
||||
f.contentBox.SetOptions(f.contentBox.Options().Include(types.GoThumbTracking))
|
||||
vcl_stringgrid_clear(f.contentBox)
|
||||
|
||||
queryTab := vcl.NewTabSheet(f.Tabs)
|
||||
queryTab.SetParent(f.Tabs)
|
||||
@ -260,16 +258,14 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
|
||||
vsplit.SetAlign(types.AlTop)
|
||||
vsplit.SetTop(2)
|
||||
|
||||
f.queryResult = vcl.NewListView(queryTab)
|
||||
f.queryResult = vcl.NewStringGrid(queryTab)
|
||||
f.queryResult.SetParent(queryTab)
|
||||
f.queryResult.SetAlign(types.AlClient) // fill remaining space
|
||||
f.queryResult.SetViewStyle(types.VsReport) // "Report style" i.e. has columns
|
||||
f.queryResult.SetAutoWidthLastColumn(true)
|
||||
f.queryResult.SetReadOnly(true)
|
||||
f.queryResult.Columns().Clear()
|
||||
f.queryResult.SetAlign(types.AlClient) // fill remaining space
|
||||
f.queryResult.BorderSpacing().SetLeft(MY_SPACING)
|
||||
f.queryResult.BorderSpacing().SetRight(MY_SPACING)
|
||||
f.queryResult.BorderSpacing().SetBottom(MY_SPACING)
|
||||
f.queryResult.SetOptions(f.queryResult.Options().Include(types.GoThumbTracking))
|
||||
vcl_stringgrid_clear(f.queryResult)
|
||||
|
||||
f.none = &noLoadedDatabase{}
|
||||
f.OnNavChange(f, nil) // calls f.none.RenderForNav and sets up status bar content
|
||||
@ -543,6 +539,10 @@ func (f *TMainForm) OnNavChange(sender vcl.IObject, node *vcl.TTreeNode) {
|
||||
ld = ndata.ld
|
||||
}
|
||||
|
||||
// Reset some controls that the render function is expected to populate
|
||||
f.propertiesBox.Clear()
|
||||
vcl_stringgrid_clear(f.contentBox)
|
||||
|
||||
ld.RenderForNav(f, ndata) // Handover to the database type's own renderer function
|
||||
|
||||
// We're in charge of common status bar text updates
|
||||
|
@ -62,3 +62,10 @@ func vcl_default_tab_background() types.TColor {
|
||||
return colors.ClBtnFace // 0x00f0f0f0
|
||||
}
|
||||
}
|
||||
|
||||
func vcl_stringgrid_clear(d *vcl.TStringGrid) {
|
||||
d.SetFixedCols(0) // No left-hand cols
|
||||
d.SetRowCount(1) // The 0th row is the column headers
|
||||
d.SetEnabled(false)
|
||||
d.Columns().Clear()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user