前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Clojure文件操作和惰性序列

Clojure文件操作和惰性序列

作者头像
lambeta
发布于 2018-08-17 03:33:27
发布于 2018-08-17 03:33:27
3.2K00
代码可运行
举报
文章被收录于专栏:编舟记编舟记
运行总次数:0
代码可运行

读取和写入文件

数据一般都是存储在纯文本文件当中,存储的形式多种多样。本文,我会介绍如何在Clojure中读取和写入这些数据。

1. 打开文件

新建文件hello.txt,放到resources目录,内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
hello world!
hello lambeta!
hello life!

新建4io.clj,输入程序:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(ns the-way-to-clojure.4io
  (:require [clojure.java.io :as io]
            [clojure.string :as str]))

(def data-file (io/resource "hello.txt"))
(slurp data-file) 

运行程序,输出如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"hello world! \nhello lambeta!\nhello life!\n"
读取所有行
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(line-seq (io/reader data-file))
;;=> ("hello world!" "hello lambeta!" "hello life!")
with-open宏

with-open宏用于自动关闭打开的文件。

1.1 读取一行,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(with-open [rdr (io/reader data-file)]
  (when-let [line (.readLine rdr)]
    (println line)))

1.2 读取多行,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
;;; read multiple lines
(with-open [rdr (io/reader data-file)]
  (loop [line (.readLine rdr)]
    (when line
      (println line)
      (recur (.readLine rdr)))))

2. 读取文件的技巧

想想读取文件可能有哪些场景?

  • 读取整个文本
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(slurp data-file)
  • 读取一行
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(with-open [rdr (io/reader data-file)]
  (first (line-seq rdr)))
;; 或者
(with-open [rdr (io/reader data-file)]
  (take 1 (line-seq rdr)))
-> "hello world!"
  • 读取前n行
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(with-open [rdr (io/reader data-file)]
  (doall (take 2 (line-seq rdr))))
-> ("hello world!" "hello lambeta!")

这里使用了(doall )方法,如果不用这个方法,在repl中求值的时候会表达式导致抛出Unhandled java.io.IOException Stream closed异常。究其缘由是(take 2 )返回了一个惰性序列,详细解释参见备注。

  • 读取前n个字符
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
with-open [rdr (io/reader data-file)]
  (loop [ch (.read rdr) len 20]
    (when-not (or (= -1 ch) (zero? len))
      (println (char ch))
      (recur (.read rdr) (dec len)))))
| h
| e
| ...
-> nil
  • 跳过特定的行

resources目录下,新建records.txt,内容即代码注释所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(defn read-records [input-file]
  "Coloured fox fur production, HOPEDALE, Labrador, 1834-1842
  #Source: C. Elton (1942) \"Voles, Mice and Lemmings\", Oxford Univ. Press
  #Table 17, p.265--266
  22
  29
  2
  16
  12
  35
  8
  83
  166"
  (letfn [(skip [lines]
            (next lines))]
         (with-open [rdr ((comp io/reader io/resource) input-file)]
           (->>
            (for [line (skip (line-seq rdr))
                  :when (not (.startsWith line "#"))]
              (read-string line))
            (apply +)))))

(read-records "records.txt")
-> 373

我们在read-records内部新建一个skip方法,顾名思义,跳过第一个元素,然后返回后面的列表。这里旨在跳过文本的声明头。:when (not ...)过滤了文本的注释部分(以#开头的行),并使用read-string转换字符串到数字类型,(for )求值完成后返回只包含数字的列表。最后,我们对列表做了一次累加操作。

我们试试非过滤而是跳过(删除)以"#"开头行的方式获取数字列表,这样更符合要求。重写with-open部分,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(with-open [rdr ((comp io/reader io/resource) input-file)]
      (apply +
             (let [lines (skip (line-seq rdr))]
               (->> lines
                    (remove (set (for [line lines :while (.startsWith line "#")] line)))
                    (map read-string)))))

或者

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(with-open [rdr ((comp io/reader io/resource) input-file)]
      (apply +
             (let [lines (skip (line-seq rdr))]
               (->> lines
                    (drop (count (for [line lines :while (.startsWith line "#")] line)))
                    (map read-string)))))

3. 读取网络文件

通过slurp读取字符串

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(slurp "http://robjhyndman.com/tsdldata/ecology1/hopedale.dat" :encoding "utf-8")

-> "Coloured fox fur production, HOPEDALE, Labrador,, 1834-1925\n#Source: C. Elton (1942) \"Voles, Mice and Lemmings\", Oxford Univ. Press\n#Table 17, p.265--266\n      22   \n...

注意,这个网页上的数据是用UTF-8编码的,所以解码读取时,也应该使用UTF-8。

4. 写入文件

  • 使用spit方法
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(spit "world.txt" "Hello, lambeta!" :append true)

运行程序之后,项目的根目录下会生成world.txt文件,内容是Hello, lambeta。spit方法其实就是向Java的BufferedWriter中写入内容。

  • 使用clojure.java.io/writer

我们在项目的根目录新建numbers.txt,内容是多行的数字对,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1.3 2.7
10000 1
-1 1

我们需要把每行两个数字,和它们相加的结果写入到sum-of_numbers.txt文件中。也就是注释中的描述。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(defn sum-number-pairs [input-file output-file]
  "Read the data from input-file, which contains two floats per line
   separated by a space.  Open file named output-file and, for each line in
   input-file, write a line to the output file that contains the two floats
   from the corresponding line of input-file plus a space and the sum of the
   two floats."
(with-open [rdr (io/reader input-file) wtr (io/writer output-file :append true)]
    (loop [line (.readLine rdr)]
      (when line
        (let [pair (map read-string
                        (str/split line #"\s"))
              first (first pair)
              second (second pair)
              sum (+ first second)]
          (.write wtr (str first " " second " " sum "\n")))
        (recur (.readLine rdr))))))

(sum-number-pairs "numbers.txt" "sum-of-numbers.txt")

with-open同时打开了一个用于读取、名为input-file的文件以及一个用于写入、名为output-file的文件,写入方式是追加:append true。随后循环读取input-file中的每行内容。若line不是nil(即存在),那么用空格分隔这行内容,得到一个数组,如:"1.3 2.7" -> ["1.3" "2.7"]。此时数组的元素类型还不是数字(Number),我们使用(map read-string )将元素转换为对应的数字类型,如:["1.3" "2.7"] -> [1.3 2.7]。之后,分别提取数组的第一、二个元素以及两者的和。最后,写入到wtr中。


注意:程序中的str/split是通过(:require [clojure.string :as str])方式引入str命名空间的。


运行程序之后,sum-of-numbers.txt中的内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1.3 2.7 4.0
10000 1 10001
-1 1 0

5. 多行记录

5.1 有结束标识

有时候,记录并不是以一行一行的方式存储在文件当中的,而是以多行数据描述一条记录。比如下面的蛋白质数据: 清单 5.1 multimol.pdb

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
COMPND      AMMONIA
ATOM      1  N  0.257  -0.363   0.000
ATOM      2  H  0.257   0.727   0.000
ATOM      3  H  0.771  -0.727   0.890
ATOM      4  H  0.771  -0.727  -0.890
END
COMPND      METHANOL
ATOM      1  C  -0.748  -0.015   0.024
ATOM      2  O  0.558   0.420  -0.278
ATOM      3  H  -1.293  -0.202  -0.901
ATOM      4  H  -1.263   0.754   0.600
ATOM      5  H  -0.699  -0.934   0.609
ATOM      6  H  0.716   1.404   0.137
END

第一行描述的是分子的名字,接下来到END为止的每行代表原子的ID、类型以及在分子中分布的[x y z]坐标。 我们需要一个函数,将数据读取出来并且以规定的格式输出,格式如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(("AMMONIA" 
    ("N" "0.257" "-0.363" "0.000")
    ("H" "0.257" "0.727" "0.000")
    ("H" "0.771" "-0.727" "0.890")
    ("H" "0.771" "-0.727" "-0.890")) 
 ("METHANOL" 
    ("C" "-0.748" "-0.015" "0.024")
    ("O" "0.558" "0.420" "-0.278")
    ("H" "-1.293" "-0.202" "-0.901")
    ("H" "-1.263" "0.754" "0.600")
    ("H" "-0.699" "-0.934" "0.609")
    ("H" "0.716" "1.404" "0.137")))

也就是说,我们需要把每条记录读入单个列表中,每个列表由分子的名称和多个(Type X Y Z)的原子列表组成。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(defn read-all-molecules [input-file]
  (map (fn [molecules]
         (let [[_ name] (str/split (first molecules) #"\s+")
               atoms (map (comp #(drop 2 %) #(str/split % #"\s+"))
                          (rest molecules))]
           (concat [name] atoms)))
       ;; 分割成多条记录
       (remove #(= % ["END"]) 
               (partition-by #(= % "END") (line-seq (io/reader input-file))))))

(read-all-molecules "multimol.pdb")

(remove #(= % ["END"]) (partition-by #(= % "END") (line-seq (io/reader input-file))))这行代码做的事情就是把文件读取出来变成一个lazy-seq,然后使用parttition-byEND进行分组,最后使用remove方法剔除掉["END"]这样的分组,得到如下中间结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(("COMPND      AMMONIA" 
  "ATOM      1  N  0.257  -0.363   0.000"
  "ATOM      2  H  0.257   0.727   0.000"
  "ATOM      3  H  0.771  -0.727   0.890"
  "ATOM      4  H  0.771  -0.727  -0.890") 
 ("COMPND      METHANOL" 
  "ATOM      1  C  -0.748  -0.015   0.024"
  "ATOM      2  O  0.558   0.420  -0.278"
  "ATOM      3  H  -1.293  -0.202  -0.901" 
  "ATOM      4  H  -1.263   0.754   0.600"
  "ATOM      5  H  -0.699  -0.934   0.609" 
  "ATOM      6  H  0.716   1.404   0.137"))

这样离我们的目标已经很近了。观察上述结果,不难发现分子的名称处于列表的第一个(first ),而原子列表可以使用(rest )获取。然后,借助(map )函数遍历所有的记录。

(let )中的第一个binding是[_ name] (str/split (first molecules) #"\s+"),首先用(split )函数分割,再使用了解构提取出分子的名称;第二个binding是原子列表的提取,我们在(split )的基础之上,使用(drop 2 )函数剔除了不用的字段,如:ATOM和1。最后使用(concat )函数将名称和原子列表的列表拼接到一起。

5.2 无结束标识

5.1中的记录项通过END标识分隔,但是事实上这是一个多余的字段,记录项可以更简练,如下: 清单 5.2 multimol-without-end-marker.pdb

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
COMPND      AMMONIA
ATOM      1  N  0.257  -0.363   0.000
ATOM      2  H  0.257   0.727   0.000
ATOM      3  H  0.771  -0.727   0.890
ATOM      4  H  0.771  -0.727  -0.890
COMPND      METHANOL
ATOM      1  C  -0.748  -0.015   0.024
ATOM      2  O  0.558   0.420  -0.278
ATOM      3  H  -1.293  -0.202  -0.901
ATOM      4  H  -1.263   0.754   0.600
ATOM      5  H  -0.699  -0.934   0.609
ATOM      6  H  0.716   1.404   0.137

现在的问题变成了没有END标识符,如何进行分组?观察不难发现以COMPND开头的数据行可以作为记录的分隔符。 使用(partition-by #(.startsWith % "COMPND") (line-seq (io/reader input-file)))进行分组,得到的结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(("COMPND      AMMONIA") 
  ("ATOM      1  N  0.257  -0.363   0.000" 
   "ATOM      2  H  0.257   0.727   0.000" 
   "ATOM      3  H  0.771  -0.727   0.890"
   "ATOM      4  H  0.771  -0.727  -0.890") 
 ("COMPND      METHANOL") 
  ("ATOM      1  C  -0.748  -0.015   0.024" 
   "ATOM      2  O  0.558   0.420  -0.278" 
   "ATOM      3  H  -1.293  -0.202  -0.901"
   "ATOM      4  H  -1.263   0.754   0.600"
   "ATOM      5  H  -0.699  -0.934   0.609"
   "ATOM      6  H  0.716   1.404   0.137"))

此时,我们对比5.1中中间结果,会发现它们极为相似。也就是说,我们稍加转换就能让两者一致,而一致的好处就是可以复用原来(map )中的逻辑。

稍稍修改原来的分组逻辑,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(map (fn [[name atoms]] (concat name atoms))
       (partition 2
                  (partition-by
                   #(.startsWith % "COMPND")
                   (line-seq (io/reader input-file)))))

我们先使用(partition 2 )将第一步得到的列表每隔两个元素划为一组,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
((("COMPND      AMMONIA") 
  ("ATOM      1  N  0.257  -0.363   0.000" 
   "ATOM      2  H  0.257   0.727   0.000" 
   "ATOM      3  H  0.771  -0.727   0.890"
   "ATOM      4  H  0.771  -0.727  -0.890")) ; 多出一对括号
 (("COMPND      METHANOL") 
  ("ATOM      1  C  -0.748  -0.015   0.024" 
   "ATOM      2  O  0.558   0.420  -0.278" 
   "ATOM      3  H  -1.293  -0.202  -0.901"
   "ATOM      4  H  -1.263   0.754   0.600"
   "ATOM      5  H  -0.699  -0.934   0.609"
   "ATOM      6  H  0.716   1.404   0.137")))

然后使用(map (fn [[name atoms]] ...)将每组里面的两个列表合成为一个列表,这样就得到和原来5.1一模一样的中间结果。

接下来,我们把转换的逻辑从(read-all-molecules )中提取出来,以便复用。改造如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(defn read-all-molecules [f input-file]
  (let [data (f input-file)]
    (map (fn [molecules]
           (let [[_ name] (str/split (first molecules) #"\s+")
                 atoms (map (comp #(drop 2 %) #(str/split % #"\s+"))
                            (rest molecules))]
             (concat [name] atoms))) data)))

定义转换逻辑,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(defn file-without-markers->multi-records [input-file]
  (map (fn [[name atoms]] (concat name atoms))
       (partition 2
                  (partition-by
                   #(.startsWith % "COMPND")
                   (line-seq (io/reader input-file))))))

最后,我们来调用的改造之后的方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(read-all-molecules 
        file-without-markers->multi-records "multimol-without-end-marker.pdb")

此时,5.1中的转换逻辑也可以提取出一个函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(defn file->multi-records
  (remove #(= % ["END"]) 
               (partition-by #(= % "END") (line-seq (io/reader input-file)))))

原来的程序就重构成了如下的模样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(read-all-molecules file->multi-records "multimol.pdb")

备注

为了清楚定位这个问题,我们需要提前了解两个知识点

  1. 什么是惰性序列?
  2. 惰性序列在repl中什么时候变现(realizes)?

惰性序列是用(lazy-seq [& body] )宏创建出来的。lazy-seq仅在需要的时候才会去调用它的body。 当repl尝试pretty-print惰性序列的结果时,才会进行变现操作。

有了上面的知识点,我们来考察with-open(take 2 (line-seq ))的关系。with-open是宏,我们使用clojure.walk/macroexpand-all展开下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(clojure.walk/macroexpand-all 
    '(with-open [rdr (io/reader data-file)]
                      (take 2 (line-seq rdr))))
                      
-> (let* [rdr (io/reader data-file)] 
        (try (do 
                (take 2 (line-seq rdr))) 
        (finally (. rdr clojure.core/close))))                      

使用(doc line-seq)查看文档,得到

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
clojure.core/line-seq
 [rdr]
Added in 1.0
  Returns the lines of text from rdr as a lazy sequence of strings.
  rdr must implement java.io.BufferedReader.

可以确认line-seq返回一个惰性的字符串序列。 再看看(doc take)的文档,得到

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
clojure.core/take
 [n]
 [n coll]
Added in 1.0
  Returns a lazy sequence of the first n items in coll, or all items if
  there are fewer than n.  Returns a stateful transducer when
  no collection is provided.

所以take返回的也是一个惰性序列,那么(do (take 2 (line-seq rdr)))(等价于(take 2 (line-seq rdr)))整个返回的就是一个惰性序列。

当我们通过repl求值with-open时,它并没有真的变现(take 2 (line-seq rdr)),而是在运行完try...finally之后,直接返回这个惰性序列作为结果。此时,repl开始尝试pretty-print (take 2 (line-seq rdr)),变现发生,但是rdr已经被关闭了,所以抛出Stream closed异常。

到这里,解决了一大半问题,但是还有一个逻辑上解释不过去的点,就是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(with-open [rdr (io/reader data-file)]
  (take 1 (line-seq rdr)))

当我们尝试(take 1 )时并不会抛出异常!也就是说(take 1 )(take 2 )的行为不同,但是(take )明明都是返回惰性序列啊?

带着这个疑惑,看看line-seq的源代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(when-let [line (.readLine rdr)]
    (cons line (lazy-seq (line-seq rdr)))))

是不是有种豁然开朗的感觉?没有也没关系,我来解释一下。 line-seqwhen-let语句并没有包在(lazy-seq )(这点可以和take的源码比较)中,这说明[line (.readline rdr)]是需要立即求值的。也就是说,我们在求值with-open时,rdr中第一行的内容会被(line-seq )给抓住了。那么当try...finally运行结束之后,pretty-print变现惰性序列时,发现第一行根本不需要从rdr中读,当然就不会抛出异常了。

明确这几点之后,我们看看(doall )为何能解决惰性序列延迟求值的问题?(doall )其实强制变现了整个惰性序列(不断调用序列的next方法),所以并不会等到with-open求值完成之后才求值。

换个角度,我们知道之所以抛出异常,是因为repl对返回的惰性序列求值了。那么如果我们不在repl中求值,程序还会抛出异常吗?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(ns the-way-to-clojure.core
  (:require [clojure.java.io :as io])
  (:gen-class))

(defn -main [& args]
  (with-open [rdr (io/reader "hello.txt")]
    (take 100 (line-seq rdr))))

接着,我们使用lein run来运行main方法。程序运行良好,因为根本没有人用到返回的惰性序列。

如果我们加一句打印语句如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(defn -main [& args]
  (println          ; 变现
      (with-open [rdr (io/reader "hello.txt")]
        (take 100 (line-seq rdr)))))

再用lein run跑一个main方法,异常又不期而遇了。因为此处的println等价于replpretty print


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016.09.04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
VBA专题05-1:一文彻底掌握用户窗体编程基础知识(上)
引子:本文改编著自Microsoft很久以前的一篇技术文章,虽然年代已久,但完全不过时。文章以完整的示例详细介绍了用户窗体的基本概念及常用技术,能够帮助你快速掌握用户窗体编程基础。
fanjy
2019/09/17
6.6K0
用户窗体示例:工作表数据与用户窗体的交互
好的网站是非常优秀的学习资源,认真研究其分享的知识是我们快速提高水平的方法之一。这个示例仍然来自于thesmallman.com,演示了用户窗体如何与工作表中的数据进行交互:如何使用Excel工作表中的数据填充用户窗体,并将编辑后的数据发送回工作表;并且在这个例中,只需在用户窗体中输入一个关键字,然后单击回车键将自动填充用户窗体。
fanjy
2022/11/16
1.5K0
用户窗体示例:工作表数据与用户窗体的交互
Excel编程周末速成班第22课:使用事件
事件是Excel编程的重要组成部分。在第20课中对事件进行过介绍,涉及到用户窗体控件以及他们可以响应的一些事件。理解和使用Excel事件是创建易于使用的响应式应用程序的重要工具。
fanjy
2021/02/05
2.9K0
Excel实战技巧63: 制作具有数据导航功能的用户窗体
本文讲述如何连接用户窗体与ADO记录集,最终创建一个与Access窗体相似的用户窗体,可以导航至前一条记录、下一条记录、第一条记录、最后一条记录,等等。
fanjy
2019/12/04
3.2K0
Excel实战技巧66:创建向导样式的数据输入窗体4
在HRWizard用户窗体中输入的一些数据是通过组合框控件显示给用户的。HRWizard工作簿文件包含一个名为ListMgr的工作表,其中包含每个列表的数据,这些数据存储在ListMgr工作表的命名区域。
fanjy
2019/12/10
1.3K0
Excel实战技巧66:创建向导样式的数据输入窗体4
Excel应用实践12:在用户窗体中添加、查找和编辑数据记录
在Excel中,我已经创建了一个输入数据的用户窗体,用于在工作记录工作表中添加新数据记录。最近,老板提出了新的需求,要通过该用户窗体能够编辑数据记录,增强其功能。
fanjy
2019/07/19
3.6K1
Excel实战技巧66:创建向导样式的数据输入窗体5
到目前为止,我们已经完成了最艰难的工作。接下来,我们来编写用户窗体代码,将已完成的对象放进HRWizard用户窗体里并使这些对象工作。
fanjy
2019/12/11
1.8K0
Excel实战技巧66:创建向导样式的数据输入窗体5
Excel VBA编程
在Excel中,数据只有文本,数值,日期值,逻辑值和错误值五种类型。但是在VBA中,数据类型跟Excel不完全相同。根据数据的特点,VBA将数据分为布尔型(boolean),字节型(byte),整数型(integer),单精度浮点型(single),双精度浮点型(double),货币型(currency),小数型(decimal),字符串型(string),日期型(date),对象型等等
全栈程序员站长
2022/08/11
46.4K0
Excel VBA编程
VBA实战技巧:正确登录后才能使用Excel工作簿
当你打开这个工作簿时,Excel会弹出一个登录框,如下图1所示。当你输入正确的用户名和密码后,才能使用这个工作簿,否则会退出。
fanjy
2022/11/16
1.3K1
VBA实战技巧:正确登录后才能使用Excel工作簿
一起学Excel专业开发21:Excel工时报表与分析系统开发(3)——自定义用户界面
一起学Excel专业开发20:Excel工时报表与分析系统开发(3)——自定义用户界面
fanjy
2019/11/07
2K0
一起学Excel专业开发21:Excel工时报表与分析系统开发(3)——自定义用户界面
VBA小技巧:扩大/缩小用户窗体
有时候,在使用VBA实现主要功能的同时,使用一些小技巧,可以起到锦上添花的效果。正如本文所介绍的技巧,可以通过一个标签控件,来扩大或者缩小用户窗体。效果如下图1所示。
fanjy
2022/11/16
1.5K0
VBA小技巧:扩大/缩小用户窗体
EXCEL VBA语句集300
        定制模块行为 (1) Option Explicit ‘强制对模块内所有变量进行声明 Option Private Module ‘标记模块为私有,仅对同一工程中其它模块有用,在宏对话框中不显示  Option Compare Text ‘字符串不区分大小写  Option Base 1 ‘指定数组的第一个下标为1 (2) On Error Resume Next ‘忽略错误继续执行VBA代码,避免出现错误消息 (3) On Error GoTo ErrorHandler ‘当错误发生时跳转到过程中的某个位置 (4) On Error GoTo 0 ‘恢复正常的错误提示 (5) Application.DisplayAlerts=False ‘在程序执行过程中使出现的警告框不显示 (6) Application.ScreenUpdating=False ‘关闭屏幕刷新 Application.ScreenUpdating=True ‘打开屏幕刷新 (7) Application.Enable.CancelKey=xlDisabled ‘禁用Ctrl+Break中止宏运行的功能  工作簿 (8) Workbooks.Add() ‘创建一个新的工作簿 (9) Workbooks(“book1.xls”).Activate ‘激活名为book1的工作簿 (10) ThisWorkbook.Save ‘保存工作簿 (11) ThisWorkbook.close ‘关闭当前工作簿 (12) ActiveWorkbook.Sheets.Count ‘获取活动工作薄中工作表数 (13) ActiveWorkbook.name ‘返回活动工作薄的名称 (14) ThisWorkbook.Name ‘返回当前工作簿名称 ThisWorkbook.FullName ‘返回当前工作簿路径和名称 (15) ActiveWindow.EnableResize=False ‘禁止调整活动工作簿的大小 (16) Application.Window.Arrange xlArrangeStyleTiled ‘将工作簿以平铺方式排列 (17) ActiveWorkbook.WindowState=xlMaximized ‘将当前工作簿最大化  工作表 (18) ActiveSheet.UsedRange.Rows.Count ‘当前工作表中已使用的行数 (19) Rows.Count ‘获取工作表的行数(注:考虑向前兼容性) (20) Sheets(Sheet1).Name= “Sum” ‘将Sheet1命名为Sum (21) ThisWorkbook.Sheets.Add Before:=Worksheets(1) ‘添加一个新工作表在第一工作表前 (22) ActiveSheet.Move After:=ActiveWorkbook. _ Sheets(ActiveWorkbook.Sheets.Count) ‘将当前工作表移至工作表的最后 (23) Worksheets(Array(“sheet1”,”sheet2”)).Select ‘同时选择工作表1和工作表2 (24) Sheets(“sheet1”).Delete或 Sheets(1).Delete ‘删除工作表1 (25) ActiveWorkbook.Sheets(i).Name ‘获取工作表i的名称 (26) ActiveWindow.DisplayGridlines=Not ActiveWindow.DisplayGridlines ‘切换工作表中的网格线显示,这种方法也可以用在其它方面进行相互切换,即相当于开关按钮 (27) ActiveWindow.DisplayHeadings=Not ActiveWindow.DisplayHeadings ‘切换工作表中的行列边框显示 (28) ActiveSheet.UsedRange.FormatConditions.Delete ‘删除当前工作表中所有的条件格式 (29) Cells.Hyperlinks.Delete ‘取消当前工作表所有超链接 (30) ActiveSheet.PageSetup.Orientation=xlLandscape 或ActiveSheet.PageSetup.Orientation=2 ‘将页面设置更改为横向 (31) ActiveSheet.PageSetup.RightFooter=ActiveWorkbook.FullName ‘在页面设置的表尾中输入文件路径 ActiveSheet.PageSetup.Le
Tony老师
2020/03/05
2.2K0
串口助手(简洁版)上位机软件零基础教程( C# + visual studio2017 )(一)[通俗易懂]
本人所在铁人战队的实验室同学们主要从事单片机的编程开发。但比赛和项目过程中,常常都需要与机器人进行人机交互。虽然实验室常用的HMI串口屏能满足我们的基本需求,但没东西在手的时候,就是个难题了。所以本文则介绍一下使用visual studio软件,进行C#上位机软件的开发入门。 以同学们常用的串口助手(简洁版)为例,来着手进行学习和入门。由于笔者知识有限,且是第一次写博客,有不足或错误之处,还请大家指出,方便修改。
全栈程序员站长
2022/09/04
7.3K0
串口助手(简洁版)上位机软件零基础教程( C# + visual studio2017 )(一)[通俗易懂]
Excel实战技巧65: 制作漂亮的用户窗体按钮——当鼠标移动到按钮上时高亮显示
在很多场合,我们都能看到这样的效果,当鼠标移动到某个元素上面时,该元素会变成另外一种颜色,达到强调的效果。下面,我们来实现当鼠标移动到用户窗体按钮上时,会使用颜色高亮显示,让用户窗体更生动,如下图1所示。
fanjy
2019/12/04
8.9K0
使用旋转按钮调节小数数字
在用户窗体中,旋转按钮控件通常只能调节整数,而本文给出的示例让旋转按钮可以调节小数数字,如下图1所示。
fanjy
2024/03/25
1730
使用旋转按钮调节小数数字
(ExcelVBA编程入门范例)
很喜爱VBA,喜欢使用她对Excel操作实现所需的功能,更喜欢使用VBA控制Excel以及实现结果后的那种感觉。 一直都想对ExcelVBA进行系统的整理和归纳,但由于对Excel及VBA了解得不够深入,总觉得无从下手。再加上又是利用少得可怜的业余时间进行学习,时断时续,学习的主线和思路也经常因为工作或其它的事情而打断。但喜欢学习的人总会挤得出时间来的,要想掌握或者是精通一门知识和技术不能有任何借口。幸运的是,有网络这个大平台,更有ExcelHome众多网友的帮助和鼓励,这几个月,总算坚持了下来。对Excel的痴迷没有停留在头脑和心中,而是体现在了具体的行动以及积极的学习和参与上来,因此,收获很大,感觉水平也有明显的提高。 现在,我计划利用点滴的业余时间,将基本的ExcelVBA操作用简短的实例进行演示,编辑成《ExcelVBA编程入门范例》,以此对ExcelVBA基础知识进行一次归纳和整理,从而理清学习ExcelVBA的线条,同时也希望能对热衷于Excel的朋友以及ExcelVBA初学者快速了解和步入ExcelVBA编程殿堂有所帮助。这是我第一次偿试对所学知识进行较大规模的整理,希望大家能多提改进意见和建议,以利于改进和提高,也有助于以后的学习和编写出更好的作品呈献给大家。
全栈程序员站长
2022/09/06
4.4K0
VBA实战技巧:根据工作表数据创建用户窗体中的控件
在一些应用场景中,我们可能会需要根据工作表中的数据来创建用户窗体中的控件。例如下图1所示,在工作表第3行中有一行标题数据,想要根据标题数量在用户窗体中创建标签和相应的文本框。
fanjy
2022/11/16
2.6K0
VBA实战技巧:根据工作表数据创建用户窗体中的控件
Excel事件示例(二)
Excel工作表中有时有单元格的值希望被锁定,输入密码后才能修改,通常我们使用”审阅“的“保护工作簿”功能来实现,那下面通过vba代码也来实现一下。
无言之月
2019/10/13
1K0
Excel编程周末速成班第3课:Excel对象模型
导语:为了帮助想要快速学会Excel VBA的朋友,特以《Excel Programming Weekend Crash Course》这本书为基础,开始整理一系列资料,在完美Excel社群上分享。一共有30课,本文为第3课,目前已在社群上发布4课。有兴趣的朋友可以到社群上学习。
fanjy
2020/11/06
5.4K0
【愚公系列】2023年11月 Winform控件专题 MaskedTextBox控件详解
Winform控件是Windows Forms中的用户界面元素,它们可以用于创建Windows应用程序的各种视觉和交互组件,例如按钮、标签、文本框、下拉列表框、复选框、单选框、进度条等。开发人员可以使用Winform控件来构建用户界面并响应用户的操作行为,从而创建功能强大的桌面应用程序。
愚公搬代码
2023/11/30
1.1K0
推荐阅读
相关推荐
VBA专题05-1:一文彻底掌握用户窗体编程基础知识(上)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档