2.自定義@Excel註解實現數據Excel形式導入導出

来源:https://www.cnblogs.com/kzf-99/archive/2023/04/07/17296402.html
-Advertisement-
Play Games

前言 這幾天在學習如何使用自定義註解實現Excel格式數據導入導出,參考的還是若依框架裡面的代碼,由於是初學,所以照貓畫虎唄,但是難受的是需要複製並根據自己項目修改作者自定義的工具類以及導入這些工具類的依賴包。由於吃了這個苦,我決定把這個艱辛的CV操作通過一張邏輯圖來表達,方便我以後復用。下麵證實開 ...


前言

  這幾天在學習如何使用自定義註解實現Excel格式數據導入導出,參考的還是若依框架裡面的代碼,由於是初學,所以照貓畫虎唄,但是難受的是需要複製並根據自己項目修改作者自定義的工具類以及導入這些工具類的依賴包。由於吃了這個苦,我決定把這個艱辛的CV操作通過一張邏輯圖來表達,方便我以後復用。下麵證實開始介紹這個功能的實現,但是由於對項目中的只是很不瞭解,我這裡簡單實現,並簡單講解,深層次的代碼我會給出,後續會繼續運用講解。整篇博客分為兩個部分,一部分是數據的導出,一部分是數據的導入。本文項目鏈接:WomPlus: 結合若依項目對原始工單項目內容進行增強 (gitee.com)

1.所需要的依賴

  在進行項目前,我們需要導入依賴才能引用具體的功能,因此第一步就是導入依賴了,這裡除了Excel需要的依賴,一些工具類的依賴也需要導入,為什麼呢?因為作者的項目寫的很細緻,比如在咋們的java中自帶有StringUtils工具類,但是作者細緻地自己寫了一個,可見其基礎功能之深呀!

<!--Excel工具類依賴-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
</dependency>

<!--常用工具類依賴-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

<!--io常用工具依賴-->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

<!--解決@ConfigurationProperties(prefix = "wo")的
Springboot Configuration Annotation Processor not found in classPath問題依賴-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
所需依賴

2.修改配置文件

  引入依賴是第一步,修改配置文件就是我們的第二步了,在這裡修改配置文件是因為在該功能的數據導入,作者是在自己自定義的RuoYiConfig中通過

@ConfigurationProperties(prefix = "wo")註解獲取配置文件中profile屬性值,即我們上傳下載文件的地址。我這裡使用的是properties為尾碼的文件,配置文件為yaml的請根據自己文件類型修改
//添加自己的項目配置類WoConfig需要配置的項目名和導出Excel形式數據下載路徑
#項目相關配置
wo.name=WO
wo.profile=G:/Desktop/base_study/project/myself_springboot_project/wom-plus/uploadPath

3.數據以Excel格式導出的Controller層

@PostMapping("/export")
@ResponseBody
public AjaxResult export(@RequestParam(value = "name", required = false) String username){
    List<SysUser> list = userDetailsService.getUserListByUsername(username);
    ExcelUtil<SysUser> util = new ExcelUtil<>(SysUser.class);
    return util.exportExcel(list, "用戶數據");
}
  在UserController的export()方法中,首先傳入一個Class.class作為ExcelUtils類的參數來new一個ExcelUtils對象util,然後傳遞一個Class的list對象和表名稱到util的exportExcel()方法中返回Class的Excel信息。

3.1 ExcelUtils的有參構造

//1.傳入需要Excel導出類的Class.class,返回一個ExcelUtil<Class>對象
public ExcelUtil(Class<T> clazz)
{
    this.clazz = clazz;
}

  這裡就是傳入一個Class類來創建該類的Excel對象,本篇博客圍繞著SysUser類實現該功能的,因此這裡傳入SysUser.class

3.2 exportExcel(List<T>list, String sheetName)

public AjaxResult exportExcel(List<T> list, String sheetName)
{
    return exportExcel(list, sheetName, StringUtils.EMPTY);
}

  在方法中,調用exportExcel(List<SysUser>list, String sheetName, String title)封裝Excel格式數據導出

3.3 exportExcel(List<T> list, String sheetName, String title)

public AjaxResult exportExcel(List<T> list, String sheetName, String title)
{
    this.init(list, sheetName, title, Type.EXPORT);
    return exportExcel();
}

  本文方法首先初始化要創建的Excel表格,然後返回exportExcel()方法來講要導出的數據寫進前面的profile路徑中

3.3.1 init(list, sheetName, title, Type.EXPORT)

public void init(List<T> list, String sheetName, String title, Type type)
{
    if (list == null)
    {
        list = new ArrayList<T>();
    }
    this.list = list;
    this.sheetName = sheetName;
    this.type = type;
    this.title = title;
    //根據要導出Excel的實體類的欄位創建欄位Class類所需要的Excel欄位
    createExcelField();
    createWorkbook();//創建一個工作薄
    createTitle();//創建Excel第一行標題
    createSubHead();//創建對象子列表名稱
}

3.3.2 exportExcel()

public AjaxResult exportExcel()
{
    OutputStream out = null;
    try
    {
        writeSheet();//寫入數據到Sheet
        String filename = encodingFilename(sheetName);//編輯文件名
        //getAbsoluteFile(filename)根據文件名稱獲取下載路徑
        //創建一個輸出流
        out = new FileOutputStream(getAbsoluteFile(filename));
        wb.write(out);//寫入Excel信息到該路徑
        return AjaxResult.success(filename);//返回導出成功信息
    }
    catch (Exception e)
    {
        log.error("導出Excel異常{}", e.getMessage());
        throw new UtilException("導出Excel失敗,請聯繫網站管理員!");
    }
    finally
    {
        IOUtils.closeQuietly(wb);//關閉工作薄對象輸出流
        IOUtils.closeQuietly(out);//關閉輸出流
    }
}
導出資料庫數據到指定路徑

3.4 @Excel和@Excels註解

package com.ku.wo.framework.aspectj.lang.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.math.BigDecimal;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import com.ku.wo.common.utils.poi.ExcelHandlerAdapter;

/**
 * 自定義導出Excel數據註解
 * 
 * @author ruoyi
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)//作用在什麼地方
public @interface Excel
{
    /**
     * 導出時在excel中排序
     */
    public int sort() default Integer.MAX_VALUE;

    /**
     * 導出到Excel中的名字.
     */
    public String name() default "";

    /**
     * 日期格式, 如: yyyy-MM-dd
     */
    public String dateFormat() default "";

    /**
     * 如果是字典類型,請設置字典的type值 (如: sys_user_sex)
     */
    public String dictType() default "";

    /**
     * 讀取內容轉表達式 (如: 0=男,1=女,2=未知)
     */
    public String readConverterExp() default "";

    /**
     * 分隔符,讀取字元串組內容
     */
    public String separator() default ",";

    /**
     * BigDecimal 精度 預設:-1(預設不開啟BigDecimal格式化)
     */
    public int scale() default -1;

    /**
     * BigDecimal 舍入規則 預設:BigDecimal.ROUND_HALF_EVEN
     */
    public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;

    /**
     * 導出時在excel中每個列的高度 單位為字元
     */
    public double height() default 14;

    /**
     * 導出時在excel中每個列的寬 單位為字元
     */
    public double width() default 16;

    /**
     * 文字尾碼,如% 90 變成90%
     */
    public String suffix() default "";

    /**
     * 當值為空時,欄位的預設值
     */
    public String defaultValue() default "";

    /**
     * 提示信息
     */
    public String prompt() default "";

    /**
     * 設置只能選擇不能輸入的列內容.
     */
    public String[] combo() default {};

    /**
     * 是否需要縱向合併單元格,應對需求:含有list集合單元格)
     */
    public boolean needMerge() default false;

    /**
     * 是否導出數據,應對需求:有時我們需要導出一份模板,這是標題需要但內容需要用戶手工填寫.
     */
    public boolean isExport() default true;

    /**
     * 另一個類中的屬性名稱,支持多級獲取,以小數點隔開
     */
    public String targetAttr() default "";

    /**
     * 是否自動統計數據,在最後追加一行統計數據總和
     */
    public boolean isStatistics() default false;

    /**
     * 導出類型(0數字 1字元串 2圖片)
     */
    public ColumnType cellType() default ColumnType.STRING;

    /**
     * 導出列頭背景顏色
     */
    public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT;

    /**
     * 導出列頭字體顏色
     */
    public IndexedColors headerColor() default IndexedColors.WHITE;

    /**
     * 導出單元格背景顏色
     */
    public IndexedColors backgroundColor() default IndexedColors.WHITE;

    /**
     * 導出單元格字體顏色
     */
    public IndexedColors color() default IndexedColors.BLACK;

    /**
     * 導出欄位對齊方式
     */
    public HorizontalAlignment align() default HorizontalAlignment.CENTER;

    /**
     * 自定義數據處理器
     */
    public Class<?> handler() default ExcelHandlerAdapter.class;

    /**
     * 自定義數據處理器參數
     */
    public String[] args() default {};

    /**
     * 欄位類型(0:導出導入;1:僅導出;2:僅導入)
     */
    Type type() default Type.ALL;

    public enum Type
    {
        ALL(0), EXPORT(1), IMPORT(2);
        private final int value;

        Type(int value)
        {
            this.value = value;
        }

        public int value()
        {
            return this.value;
        }
    }

    public enum ColumnType
    {
        NUMERIC(0), STRING(1), IMAGE(2);
        private final int value;

        ColumnType(int value)
        {
            this.value = value;
        }

        public int value()
        {
            return this.value;
        }
    }
}
//@Excels
package com.ku.wo.framework.aspectj.lang.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Excel註解集
 * 
 * @author ruoyi
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Excels
{
    Excel[] value();
}
@Excel和@Excels

3.5 運行結果

4.數據以Excel格式導出的Controller層

@PostMapping("/importData")
    @ResponseBody
    public AjaxResult importData(MultipartFile file) throws Exception {
        ExcelUtil<SysUser> util = new ExcelUtil<>(SysUser.class);
        List<SysUser> userList = util.importExcel(file.getInputStream());
        String message = userDetailsService.importUser(userList);
        return AjaxResult.success(message);
    }
  在UserController的importData()方法中,首先傳入一個Class.class作為ExcelUtils類的參數來new一個ExcelUtils對象util,然後傳入一個要導入的Excel形式的表格數據作為utils.importData()函數的輸入流is,importData()方法根據is返回Excel表格數據中的Class類的list對象,接著傳入一個Class的list對象和一個關於是否支持更新資料庫已有數據的布爾變數到importUser()方法中,該方法根據這兩個參數來返回插入成功或失敗的信息message,最後傳入message到AjaxResult.success()方法返回導入數據成功消息。

4.1 importExcel(InputStream)

public List<T> importExcel(InputStream is) throws Exception
{
    return importExcel(is, 0);
}

public List<T> importExcel(InputStream is, int titleNum) throws Exception
{
    return importExcel(StringUtils.EMPTY, is, titleNum);
}

//核心實現代碼
public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception
{
    this.type = Type.IMPORT;
    this.wb = WorkbookFactory.create(is);
    List<T> list = new ArrayList<T>();
    // 如果指定sheet名,則取指定sheet中的內容 否則預設指向第1個sheet
    Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);
    if (sheet == null)
    {
        throw new IOException("文件sheet不存在");
    }
    boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook);
    Map<String, PictureData> pictures;
    if (isXSSFWorkbook)
    {
        pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb);
    }
    else
    {
        pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb);
    }
    // 獲取最後一個非空行的行下標,比如總行數為n,則返回的為n-1
    int rows = sheet.getLastRowNum();

    if (rows > 0)
    {
        // 定義一個map用於存放excel列的序號和field.
        Map<String, Integer> cellMap = new HashMap<String, Integer>();
        // 獲取表頭
        Row heard = sheet.getRow(titleNum);
        for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
        {
            Cell cell = heard.getCell(i);
            if (StringUtils.isNotNull(cell))
            {
                String value = this.getCellValue(heard, i).toString();
                cellMap.put(value, i);
            }
            else
            {
                cellMap.put(null, i);
            }
        }
        // 有數據時才處理 得到類的所有field.
        List<Object[]> fields = this.getFields();
        Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();
        for (Object[] objects : fields)
        {
            Excel attr = (Excel) objects[1];
            Integer column = cellMap.get(attr.name());
            if (column != null)
            {
                fieldsMap.put(column, objects);
            }
        }
        for (int i = titleNum + 1; i <= rows; i++)
        {
            // 從第2行開始取數據,預設第一行是表頭.
            Row row = sheet.getRow(i);
            // 判斷當前行是否是空行
            if (isRowEmpty(row))
            {
                continue;
            }
            T entity = null;
            for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet())
            {
                Object val = this.getCellValue(row, entry.getKey());

                // 如果不存在實例則新建.
                entity = (entity == null ? clazz.newInstance() : entity);
                // 從map中得到對應列的field.
                Field field = (Field) entry.getValue()[0];
                Excel attr = (Excel) entry.getValue()[1];
                // 取得類型,並根據對象類型設置值.
                Class<?> fieldType = field.getType();
                if (String.class == fieldType)
                {
                    String s = Convert.toStr(val);
                    if (StringUtils.endsWith(s, ".0"))
                    {
                        val = StringUtils.substringBefore(s, ".0");
                    }
                    else
                    {
                        String dateFormat = field.getAnnotation(Excel.class).dateFormat();
                        if (StringUtils.isNotEmpty(dateFormat))
                        {
                            val = parseDateToStr(dateFormat, val);
                        }
                        else
                        {
                            val = Convert.toStr(val);
                        }
                    }
                }
                else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val)))
                {
                    val = Convert.toInt(val);
                }
                else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val)))
                {
                    val = Convert.toLong(val);
                }
                else if (Double.TYPE == fieldType || Double.class == fieldType)
                {
                    val = Convert.toDouble(val);
                }
                else if (Float.TYPE == fieldType || Float.class == fieldType)
                {
                    val = Convert.toFloat(val);
                }
                else if (BigDecimal.class == fieldType)
                {
                    val = Convert.toBigDecimal(val);
                }
                else if (Date.class == fieldType)
                {
                    if (val instanceof String)
                    {
                        val = DateUtils.parseDate(val);
                    }
                    else if (val instanceof Double)
                    {
                        val = DateUtil.getJavaDate((Double) val);
                    }
                }
                else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
                {
                    val = Convert.toBool(val, false);
                }
                if (StringUtils.isNotNull(fieldType))
                {
                    String propertyName = field.getName();
                    if (StringUtils.isNotEmpty(attr.targetAttr()))
                    {
                        propertyName = field.getName() + "." + attr.targetAttr();
                    }
                    else if (StringUtils.isNotEmpty(attr.readConverterExp()))
                    {
                        val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
                    }
                    else if (StringUtils.isNotEmpty(attr.dictType()))
                    {
                        val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
                    }
                    else if (!attr.handler().equals(ExcelHandlerAdapter.class))
                    {
                        val = dataFormatHandlerAdapter(val, attr);
                    }
                    else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures))
                    {
                        PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey());
                        if (image == null)
                        {
                            val = "";
                        }
                        else
                        {
                            byte[] data = image.getData();
                            val = FileUtils.writeImportBytes(data);
                        }
                    }
                    ReflectUtils.invokeSetter(entity, propertyName, val);
                }
            }
            list.add(entity);
        }
    }
    return list;
}
importExcel

  該方法主要是通過傳入的文件獲取Excel表格中的數據,這裡是真正導入數據的核心,我暫時還沒想清楚。

4.2 importUser(InputStream)

String importUser(List<SysUser> userList);
@Override
    public String importUser(List<SysUser> userList) {
        //此處的isNull判斷的是一個對象,此時我傳入的是一個用戶list對象
        if (StringUtils.isNull(userList) || userList.size() == 0){
            throw new ServiceException("導入用戶數據不能為空!");
        }
        int successNum = 0;
        //StringBuilder字元拼接工具類,是一個可變類不安全,StringBuffer是一個不可變的安全字元拼接工具類
        StringBuilder successMsg = new StringBuilder();
        for (SysUser user: userList) {
            //因為前面驗證了該list對象不為空,故直接插入
            userMapper.insertUser(user);
            successNum++;
            successMsg.append("<br/>" + successNum + "、賬號 " + user.getUsername() + " 導入成功");
        }
        successMsg.insert(0, "恭喜你,數據已經全部導入成功!共" + successNum + "條,數據如下:");
        return successMsg.toString();
    }
Excel表格數據插入到對應資料庫的數據表

  該方法實現將Excel中獲取的數據導入到對應資料庫的數據表中,並返回導入成功的響應信息。

4.3 運行結果

5.參考項目連鏈接

RuoYi:

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ps 命令速查備忘清單 Linux我們提供了一個名為 ps 的實用程式,用於查看與系統上的進程相關的信息,它是 Process Status 的縮寫這份 ps 命令備忘清單的快速參考列表,包含常用選項和示例。入門,為開發人員分享快速參考備忘單。 開發速查表大綱 入門 語法 示例 查看系統上的每個進程 ...
  • Perl語言線上運行編譯,是一款可線上編程編輯器,在編輯器上輸入Perl語言代碼,點擊運行,可線上編譯運行Perl語言,Perl語言代碼線上運行調試,Perl語言線上編譯,可快速線上測試您的Perl語言代碼,線上編譯Perl語言代碼發現是否存在錯誤,如果代碼測試通過,將會輸出編譯後的結果。 該線上工 ...
  • 簡介 ChatGPT Java版SDK開源地址:https://github.com/Grt1228/chatgpt-java ,目前收穫將近1000個star。 有bug歡迎朋友們指出,互相學習,所有咨詢全部免費。 最新版:1.0.10 <dependency> <groupId>com.unfb ...
  • 本文分享自天翼雲開發者社區@《Springfox與SpringDoc——swagger如何選擇(SpringDoc入門)》,作者: 才開始學技術的小白 0.引言 之前寫過一篇關於swagger(實際上是springfox)的使用指南(https://www.ctyun.cn/developer/ar ...
  • 1. execute 和 submit 的區別 前面說了還需要介紹多線程中使用 execute 和 submit 的區別(這兩個方法都是線程池 ThreadPoolExecutor 的方法)。 1.1 方法來源不同 execute 方法是線程池的頂層介面 Executor 定義的,在 ThreadP ...
  • 數字營銷,也就是網路營銷,即線上推廣你的產品或服務。 其核心的是:在正確的客戶面前線上推廣正確的產品 Right Customer Right Product Online 一、關於市場研究 網路營銷中,最重要的第一步是市場研究(Marketing Research)。 包括以下兩個方面: 誰是目標 ...
  • Python的反射機制可以動態獲取對象信息以及動態調用對象,本文介紹如何獲取對象中的函數註釋信息以及參數信息。 定義一個Person類: class Person(): def talk(self, name, age, height=None): """talk function :return: ...
  • 原文:blog.csdn.net/h254931252/article/details/109257998 在實際工作中,重處理是一個非常常見的場景,比如: 發送消息失敗。 調用遠程服務失敗。 爭搶鎖失敗。 這些錯誤可能是因為網路波動造成的,等待過後重處理就能成功。通常來說,會用try/catch, ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...