1. 程式人生 > >通用的將Excel匯入資料集的方法

通用的將Excel匯入資料集的方法

開發中經常有需要將Excel匯入資料集的需要,但每張Excel的欄位都會不同,常規的做法有兩種:
一、針對每一張Excel的欄位與資料集欄位位置在程式中寫好,一一對應匯入
二、針對每一張Excel的欄位與資料集欄位配置好應用引數
這兩種方法都比較麻煩,很不靈活,所以我寫了一個比較通用的方法,只要符合以下條件就可以直接使用:

 1、Excel第一行是欄位標題,第二行開始是資料

 2、應用程式資料集顯示控制元件如(dbgrid,dxdbgrid等)名稱與Excel首行標題名一致(順序可以不同,數量也可以不同,如Excel的欄位:工號,姓名,年齡;顯示控制元件欄位:姓名,工號,年齡,建立時間;這也是可以的)

以下為delphi 程式碼:
procedure ExportExcelToCDS(mygrid: TdxDBGrid; filename: string);
var
  i,j,row,col,ValidFNCount:integer;
  MyExcel,Sheet:Variant;
  str1,Prompt,ts:string;
  fieldnames:array of string;
  fieldList:array of string;
  ColIndex:array of Integer;//Excel列序號
  tmpcds:TDataSet;
  tmpds:TDataSource;
  CelValue:string;

  //搜尋Excel的標題是否有對應到資料表中的欄位
  procedure SetFieldList;
  var
    t,t2,js:Integer;
    str1,str2:string;
  begin
    //搜尋Excel中的有效欄位
    for t:=1 to col do
    begin
      str1:=StringReplace(Sheet.Cells[1,t].Text,' ','',[rfReplaceAll]);
      for t2:=0 to mygrid.ColumnCount-1 do
      begin
        str2:=StringReplace(mygrid.Columns[t2].Caption,' ','',[rfReplaceAll]);
        if str1=str2 then
        begin
          ValidFNCount:=ValidFNCount+1;
          Break;
        end;
      end;
    end;
    SetLength(fieldList,ValidFNCount);
    SetLength(ColIndex,ValidFNCount);
    js:=0;
    for t:=1 to col do
    begin
      str1:=StringReplace(Sheet.Cells[1,t].Text,' ','',[rfReplaceAll]);
      for t2:=0 to mygrid.ColumnCount-1 do
      begin
        str2:=StringReplace(mygrid.Columns[t2].Caption,' ','',[rfReplaceAll]);
        if str1=str2 then
        begin
          fieldList[js]:=mygrid.Columns[t2].FieldName;//欄位
          fieldnames[js]:=mygrid.Columns[t2].Caption;//欄位顯示名稱
          ColIndex[js]:=t;//Excel列序號1...
          js:=js+1;
          Break;
        end;
      end;
    end;
  end;

  function CheckField:string;
  var
    t:Integer;
    str1:string;
  begin
    for t:=1 to col do
    begin
      str1:=stringreplace(Sheet.Cells[1,t].Text,' ','',[rfReplaceAll]);
      if str1=fieldnames[i] then
      begin
        Break;
      end;
      Result:=str1;
    end;
  end;
  //Excel列名至少有一個與grid中的欄位相對應,是否不執行資料追加操作
  function CheckFieldArray:Boolean;
  var
    t,t2:integer;
  begin
    t2:=0;
    for t:=0 to col-1 do
    begin
      if Trim(fieldList[t])<>'' then
      begin
        t2:=1;
        Break;
      end;
    end;
    if t2=0 then
      Result:=true
    else
      Result:=False;
  end;
begin
  if UpperCase(ExtractFileExt(filename))<>uppercase('.xlsx') then
  begin
    ExportXLSToCDS(mygrid,filename);
    Exit;
  End;
    //支援Excel2007格式
  tmpcds:=mygrid.DataSource.DataSet;
  tmpds:=mygrid.DataSource;
  try
    MyExcel:=CreateOleObject('Excel.Application');
  except
    ts:='請安裝Excel';
    MessageDlg(ts,mtWarning,[mbok],0);
    Exit;
  end;
  tmpcds.DisableControls;
  SetLength(fieldnames,mygrid.ColumnCount);
  try
    for i:=0 to mygrid.ColumnCount-1 do
    begin
      if mygrid.Columns[i].Visible then
         fieldnames[i]:=stringreplace(mygrid.Columns[i].Caption,' ','',[rfReplaceAll]);
    end;
    str1:=CheckField;
    if str1<>'' then
    begin
      MessageDlg('Excel中的'+str1+'不正確',mtError,[mbOK],0);
      Exit;
    end;
    MyExcel.Workbooks.open(filename);
    Sheet:=MyExcel.ActiveSheet;
    row:=Sheet.UsedRange.Rows.Count;//行數
    col:=Sheet.UsedRange.Columns.Count;//列數
    if row<=1 then
    begin
      Prompt:='Excel中至少有一條資料'+#13+'第一行是標題,其它行為資料行'+#13+'條件不符,操作取消';
      MessageDlg(Prompt,mtWarning,[mbOK],0);
      Exit;
    end;
    if col<=1 then
    begin
      Prompt:='Excel中至少有一列資料'+#13+'條件不符,操作取消';
      MessageDlg(Prompt,mtWarning,[mbOK],0);
      Exit;
    end;
    SetFieldList;
    if CheckFieldArray then
    begin
      Prompt:='Excel中第一行中的列名至少有一個與列表中欄位相同'+#13+'條件不符,操作取消';
      MessageDlg(Prompt,mtWarning,[mbOK],0);
      Exit;
    end;
    Screen.Cursor:=crHourGlass;
    if not tmpcds.Active then
      tmpcds.Open;
    for i:=2 to row do
    begin
      Application.ProcessMessages;
      CelValue:=Trim(Sheet.Cells[i,0].Text);
      if (CelValue='') then Continue;
      tmpcds.Append;
      for j:=0 to ValidFNCount-1 do
      begin
        Application.ProcessMessages;
        CelValue:=Trim(Sheet.Cells[i,ColIndex[j]].Text);
        try
          //匯入的資料文字不可以有公式,否則會出錯
          if (CelValue<>'') then
          begin
            case tmpcds.FieldByName(fieldList[j]).DataType of
              ftString:
                 tmpcds.FieldByName(fieldList[j]).AsString:=CelValue;
              ftSmallint,ftInteger,ftWord,ftBoolean,ftFloat,ftCurrency,
              ftBCD,ftBytes:
                tmpcds.FieldByName(fieldList[j]).Value:=StrToFloat(CelValue);
              ftDate,ftTime,ftDateTime:
                tmpcds.FieldByName(fieldList[j]).AsDateTime:=Sheet.Cells[i,ColIndex[j]].Value;
            end;
          end;
        except
          on E:Exception do
          begin
            MessageDlg(E.Message+#13+'寫入欄位'+fieldList[j]+'時出錯,寫入內容:'
               +vartostr(CelValue)+#13+'Excel出錯行列:'+inttostr(i)+','+inttostr(j),mtError,[mbOK],0);
          end;
        end;
      end;
      tmpcds.Post;
    end;
  finally
    tmpcds.EnableControls;
    MyExcel.Workbooks.close;
    MyExcel.quit;
    Sheet:=Unassigned;
    MyExcel:=Unassigned;
    Screen.Cursor:=crDefault;
    MessageDlg('資料匯入完畢',mtInformation,[mbOK],0);
  end;
end;

以上方法通過建立Excel物件匯入其資料的,未使用第三方控制元件,其傳入引數dxgrid可以改成您應用程式自已的控制元件型別,只要其帶有datasource.dataset屬性即可,比較靈活,但資料量比較大時可能比較慢,所以我做了一下改進,使用第三方控制元件讀取Excel,再執行匯入操作,這樣速度非常快,以下是我改進的使用TcxSpreadSheet控制元件讀取Excel的方法:

procedure ExportXLSToCDS(mygrid:TdxDBGrid;filename:string);//將excel匯入資料集
var
  i,j,row,col,ValidFNCount:integer;
  MyExcel:TcxSpreadSheet;
  str1,Prompt:string;
  fieldnames:array of string;
  fieldList:array of string;
  ColIndex:array of Integer;//Excel列序號
  tmpcds:TDataSet;
  tmpds:TDataSource;
  CelValue:string;

  //搜尋Excel的標題是否有對應到資料表中的欄位
  procedure SetFieldList;
  var
    t,t2,js:Integer;
    str1,str2:string;
  begin
    //搜尋Excel中的有效欄位
    for t:=0 to col-1 do
    begin
      str1:=StringReplace(MyExcel.Sheet.getcellobject(t,0).Text,' ','',[rfReplaceAll]);
      for t2:=0 to mygrid.ColumnCount-1 do
      begin
        str2:=StringReplace(mygrid.Columns[t2].Caption,' ','',[rfReplaceAll]);
        if str1=str2 then
        begin
          ValidFNCount:=ValidFNCount+1;
          Break;
        end;
      end;
    end;
    SetLength(fieldList,ValidFNCount);
    SetLength(ColIndex,ValidFNCount);
    js:=0;
    for t:=0 to col-1 do
    begin
      str1:=StringReplace(MyExcel.Sheet.getcellobject(t,0).Text,' ','',[rfReplaceAll]);
      for t2:=0 to mygrid.ColumnCount-1 do
      begin
        str2:=StringReplace(mygrid.Columns[t2].Caption,' ','',[rfReplaceAll]);
        if str1=str2 then
        begin
          fieldList[js]:=mygrid.Columns[t2].FieldName;//欄位
          fieldnames[js]:=mygrid.Columns[t2].Caption;//欄位顯示名稱
          ColIndex[js]:=t;//Excel列序號1...
          js:=js+1;
          Break;
        end;
      end;
    end;
  end;

  function CheckField:string;
  var
    t:Integer;
    str1:string;
  begin
    for t:=0 to col-1 do
    begin
      str1:=stringreplace(MyExcel.Sheet.getcellobject(t,0).Text,' ','',[rfReplaceAll]);
      if str1=fieldnames[i] then
      begin
        Break;
      end;
      Result:=str1;
    end;
  end;
  //Excel列名至少有一個與grid中的欄位相對應,是否不執行資料追加操作
  function CheckFieldArray:Boolean;
  var
    t,t2:integer;
  begin
    t2:=0;
    for t:=0 to col-1 do
    begin
      if Trim(fieldList[t])<>'' then
      begin
        t2:=1;
        Break;
      end;
    end;
    if t2=0 then
      Result:=true
    else
      Result:=False;
  end;

begin
  tmpcds:=mygrid.DataSource.DataSet;
  tmpds:=mygrid.DataSource;
  MyExcel:=TcxSpreadSheet.Create(nil);
  tmpcds.DisableControls;
  SetLength(fieldnames,mygrid.ColumnCount);
  try
    for i:=0 to mygrid.ColumnCount-1 do
    begin
      if mygrid.Columns[i].Visible then
         fieldnames[i]:=stringreplace(mygrid.Columns[i].Caption,' ','',[rfReplaceAll]);
    end;
    str1:=CheckField;
    if str1<>'' then
    begin
      MessageDlg('Excel中的'+str1+'不正確',mtError,[mbOK],0);
      Exit;
    end;
    MyExcel.LoadFromFile(filename);
    row:=MyExcel.Sheet.ContentRowCount;//行數
    col:=MyExcel.Sheet.ContentColCount;//列數
    if row<=1 then
    begin
      Prompt:='Excel中至少有一條資料'+#13+'第一行是標題,其它行為資料行'+#13+'條件不符,操作取消';
      MessageDlg(Prompt,mtWarning,[mbOK],0);
      Exit;
    end;
    if col<=1 then
    begin
      Prompt:='Excel中至少有一列資料'+#13+'條件不符,操作取消';
      MessageDlg(Prompt,mtWarning,[mbOK],0);
      Exit;
    end;
    SetFieldList;
    if CheckFieldArray then
    begin
      Prompt:='Excel中第一行中的列名至少有一個與列表中欄位相同'+#13+'條件不符,操作取消';
      MessageDlg(Prompt,mtWarning,[mbOK],0);
      Exit;
    end;
    Screen.Cursor:=crHourGlass;
    if not tmpcds.Active then
      tmpcds.Open;
    for i:=1 to row-1 do
    begin
      Application.ProcessMessages;
      CelValue:=Trim(MyExcel.Sheet.getcellobject(ColIndex[0],i).DisplayText);
      if (CelValue='') then Continue;
      tmpcds.Append;
      for j:=0 to ValidFNCount-1 do
      begin
        Application.ProcessMessages;
        CelValue:=MyExcel.Sheet.getcellobject(ColIndex[j],i).DisplayText;
        try
          //匯入的資料文字不可以有公式,否則會出錯
          if VarToStr(CelValue)<>'' then
          begin
            case tmpcds.FieldByName(fieldList[j]).DataType of
              ftString:
                 tmpcds.FieldByName(fieldList[j]).AsString:=CelValue;
              ftSmallint,ftInteger,ftWord,ftBoolean,ftFloat,ftCurrency,
              ftBCD,ftBytes:
                tmpcds.FieldByName(fieldList[j]).Value:=StrToFloat(CelValue);
              ftDate,ftTime,ftDateTime:
                tmpcds.FieldByName(fieldList[j]).AsDateTime:=MyExcel.Sheet.getcellobject(ColIndex[j],i).DateTime;
            end;
          end;
        except
          on E:Exception do
          begin
            MessageDlg(E.Message+#13+'寫入欄位'+fieldList[j]+'時出錯,寫入內容:'
               +vartostr(CelValue)+#13+'Excel出錯行列:'+inttostr(i)+','+inttostr(j),mtError,[mbOK],0);
          end;
        end;
      end;
      tmpcds.Post;
    end;
  finally
    tmpcds.EnableControls;
    Screen.Cursor:=crDefault;
    FreeAndNil(MyExcel);
    MessageDlg('資料匯入完畢',mtInformation,[mbOK],0);
  end;
end;
注:我使用的TcxSpreadSheet版本比較低,只支援.xls格式.各種開發工具使用以上方法只要將程式碼稍做改動即可。