怎样怎么才能让蜗牛出来把指定帧数的结构取出来

修改beacon帧结构(2)帧的接收与帧信息提取过程【Linux内核-OpenWRT】
上一章说到了Linux内核网络子系统中beacon帧是如何产生与发送的。下面我们来看一看beacon帧是如何接收并提取信息的。Linux内核是通过中断来对接收到的数据进行响应的。当硬件检测到有接收数据的时候,产生一个中断,中断触发下半部的tasklet机制,在802.11协议栈这里会调用ieee80211_tasklet_handler()函数。我们来看一看函数体:
static void ieee80211_tasklet_handler(unsigned long data)
struct ieee80211_local *local = (struct ieee80211_local *)
struct sk_buff *
while ((skb = skb_dequeue(&local-&skb_queue)) ||
(skb = skb_dequeue(&local-&skb_queue_unreliable))) {
switch (skb-&pkt_type) {
case IEEE80211_RX_MSG:
/* Clear skb-&pkt_type in order to not confuse kernel
* netstack. */
skb-&pkt_type = 0;
ieee80211_rx(&local-&hw, skb);
case IEEE80211_TX_STATUS_MSG:
系统收到数据时会开辟一个sk_buff缓存空间进行数据的存储,ieee80211_tasklet_handler()触发后对sk_buff中存储的数据帧进行判断,如果是接收来的数据(MPDU),则进入ieee80211_rx()函数:
void ieee80211_rx(struct ieee80211_hw *hw, struct sk_buff *skb){
struct ieee80211_local *local = hw_to_local(hw);
struct ieee80211_rate *rate = NULL;
struct ieee80211_supported_band *
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
__ieee80211_rx_handle_packet(hw, skb);
rcu_read_unlock();
kfree_skb(skb);
EXPORT_SYMBOL(ieee80211_rx);
下面我们进入ieee80211_scan_rx()函数看看帧信息是如何被扫描和提取出来的(具体细节请看注释):void ieee80211_scan_rx(struct ieee80211_local *local, struct sk_buff *skb)
struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_sub_if_data *sdata1, *sdata2;
struct ieee80211_mgmt *mgmt = (void *)skb-&
/*接收到的beacon帧的具体信息存储在sk_buff的data成员变量里,data是一个char型指针,指向具体的数据区域,这里用ieee80211_mgmt结构体将data中的前面一部分信息提取出来,分别放入ieee80211_mgmt的各个成员分量中,包括MAC头、固有字段以及可选字段的起始地址*/
struct ieee80211_bss *
struct ieee80211_channel *
struct ieee802_11_
if (skb-&len & 24 ||
(!ieee80211_is_probe_resp(mgmt-&frame_control) &&
!ieee80211_is_beacon(mgmt-&frame_control)))
sdata1 = rcu_dereference(local-&scan_sdata);
sdata2 = rcu_dereference(local-&sched_scan_sdata);
if (likely(!sdata1 && !sdata2))
if (ieee80211_is_probe_resp(mgmt-&frame_control)) {
/* ignore ProbeResp to foreign address */
if ((!sdata1 || !ether_addr_equal(mgmt-&da, sdata1-&vif.addr)) &&
(!sdata2 || !ether_addr_equal(mgmt-&da, sdata2-&vif.addr)))
elements = mgmt-&u.probe_resp.
baselen = offsetof(struct ieee80211_mgmt, u.probe_resp.variable);
baselen = offsetof(struct ieee80211_mgmt, u.beacon.variable);
/*beacon帧分为MAC头部、固有字段(也称强制字段)和可选字段,这里将可选字段的起始地址取出来赋给elements指针,用于可选字段信息的提取*/
elements = mgmt-&u.beacon.
if (baselen & skb-&len)
ieee802_11_parse_elems(elements, skb-&len - baselen, false, &elems);
/*进入可选字段提取信息,由于我们是要在beacon帧中添加信息,而添加信息只能在可选字段进行,因此需要重点关注这个函数*/
ieee80211_rx()函数再调用__ieee80211_rx_handle_packet(),__ieee80211_rx_handle_packet()是接收帧的处理函数,会对帧类型进行判断,如果检测出该帧是beacon帧(或sta主动扫描后从AP端返回的响应帧),则进入ieee80211_scan_rx()函数对帧信息进行扫描。
static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
struct sk_buff *skb)
struct ieee80211_local *local = hw_to_local(hw);
struct ieee80211_sub_if_data *
struct ieee80211_hdr *
struct ieee80211_rx_
struct ieee80211_sub_if_data *
struct sta_info *sta, *tmp, *prev_
int err = 0;
hdr = (struct ieee80211_hdr *)skb-&
ieee80211_parse_qos(&rx);
ieee80211_verify_alignment(&rx);
if (unlikely(ieee80211_is_probe_resp(hdr-&frame_control) ||
ieee80211_is_beacon(hdr-&frame_control)))
ieee80211_scan_rx(local, skb);
/*扫描帧信息*/
if (ieee80211_is_data(fc)) {
dev_kfree_skb(skb);
接收到的beacon帧的具体信息存储在sk_buff的data成员变量里,data是一个char型指针,指向具体的数据区域。函数首先用ieee80211_mgmt结构体将data中的前面一部分信息提取出来,分别放入ieee80211_mgmt的各个成员分量中,包括MAC头、固有字段以及可选字段的起始地址。进而将可选字段的起始地址取出来赋给elements指针,用于可选字段信息的提取。然后进入可选字段提取信息,由于我们是要在beacon帧中添加信息,而添加信息只能在可选字段进行,因此需要重点关注这个函数。下面就来看看函数ieee802_11_parse_elems():
static inline void ieee802_11_parse_elems(const u8 *start, size_t len,
bool action,
struct ieee802_11_elems *elems)
ieee802_11_parse_elems_crc(start, len, action, elems, 0, 0);
ieee802_11_parse_elems_crc()函数直接被调用:
u32 ieee802_11_parse_elems_crc(const u8 *start, size_t len, bool action,
struct ieee802_11_elems *elems,
u64 filter, u32 crc)
size_t left =
const u8 *pos =
bool calc_crc = filter != 0;
DECLARE_BITMAP(seen_elems, 256);
const u8 *
bitmap_zero(seen_elems, 256);
memset(elems, 0, sizeof(*elems));
elems-&ie_start =
elems-&total_len =
while (left &= 2) {
bool elem_parse_
id = *pos++;
elen = *pos++;
left -= 2;
if (elen & left) {
elems-&parse_error =
switch (id) {
case WLAN_EID_SSID:
case WLAN_EID_SUPP_RATES:
case WLAN_EID_FH_PARAMS:
case WLAN_EID_DS_PARAMS:
case WLAN_EID_CF_PARAMS:
case WLAN_EID_TIM:
case WLAN_EID_SECONDARY_CHANNEL_OFFSET:
case WLAN_EID_WIDE_BW_CHANNEL_SWITCH:
* not listing WLAN_EID_CHANNEL_SWITCH_WRAPPER -- it seems possible
* that if the content gets bigger it might be needed more than once
if (test_bit(id, seen_elems)) {
elems-&parse_error =
if (calc_crc && id & 64 && (filter & (1ULL && id)))
crc = crc32_be(crc, pos - 2, elen + 2);
elem_parse_failed =
switch (id) {
case WLAN_EID_SSID:
elems-&ssid =
elems-&ssid_len =
case WLAN_EID_TIMEOUT_INTERVAL:
if (elen &= sizeof(struct ieee80211_timeout_interval_ie))
elems-&timeout_int = (void *)
elem_parse_failed =
if (elem_parse_failed)
elems-&parse_error =
__set_bit(id, seen_elems);
if (left != 0)
elems-&parse_error =
从函数中可以看出,系统获取了指向可选字段区域的指针以后,通过指针偏移的方法来读取元素标识符(Element ID)和字段长度(Length),然后读取字段内容,获取了一个字段的信息以后再将指针移到下一个字段的开头处(Element ID处)进行信息的读取。
这就是beacon帧接收和帧信息提取的整个过程。
写到这里,我们大致可以总结出两种信息读取的方法:
1)第一个方法是使用结构体从数据区域直接取数据,该方法可以快速的提取出位于数据区域前列的MAC头和强制字段,并将其分别放入结构体内对应的数据成员中。该方法提取数据较为迅速方便,但欠缺灵活性,因此用在固定字段的读取上(数据结构可以固定)。
2)可选字段的字段种类和长度都是不固定的,不能用结构体从数据区域直接读取,因此需要用到第二种方法:指针偏移。通过指针偏移将指针指向的数据区域的信息读取出来。虽然较为繁琐,但灵活性较高,适用于可选字段区域。
没有更多推荐了,
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!Android源码阅读——GIF解码(如何提取各帧图片) - 简书
Android源码阅读——GIF解码(如何提取各帧图片)
版权声明:本文为博主原创文章,未经博主允许不得转载。
系列博客:
大家要是看到有错误的地方或者有啥好的建议,欢迎留言评论
前言:阅读优秀的源码可以大大提高我们的开发水平,遂开个新坑 记录优秀源码(Android源代码、各种开源库等等)的分析和解读,学习别人是怎样实现某个功能的。本期我们的主角是 GIF的解码,我们将从GIF解码的源码 GifDecoder入手,分析其实现的原理和过程,希望能帮到大家~( GifDecoder源码(博主已对源码里面各方法及参数进行了注释,请放心食用 ~)链接已在上方贴出来了,该源码参考了解析GIF部分的代码,但由于是很久之前看到的,具体出处已无从考证,有知道的小伙伴可以留言告诉我)
GIF结构简述
GifDecoder的初始化
判断传入文件格式
读取GIF大小、颜色深度等全局属性
提取各帧图片
GIF结构简述
相关博文链接
在分析源码之前,我们得先对GIF图片的构成有一个初步的了解(详细解析请看上方链接),见下图
图中加粗部分既是保存我们所需要提取图片的地方(一帧图像对应一个图像块)。虽然我们知道了存储每一帧图像信息的位置,但我们不能直接从中取出图片,因为在计算机中,所有的文件都是以二进制的形式存储的,而Java读取文件需要按顺序一个一个字节地读。因此GIF的解码过程,实际上就是从文件头(File Header)开始,按顺序遍历每一个字节,当读到我们需要的信息(图像数据)时,就将其提取出来。下面我们就开始分析GifDecoder是如何实现GIF解码的
GifDecoder的初始化
先来看看GifDecoder的初始化和使用示例,代码如下
InputStream is = getContentResolver().openInputStream(uri);
GifDecoder gifDecoder = new GifDecoder();
int code = gifDecoder.read(is);
if (code == GifDecoder.STATUS_OK) {//解码成功
GifDecoder.GifFrame[] frameList = gifDecoder.getFrames();
} else if (code == gifDecoder.STATUS_FORMAT_ERROR) {//图片格式不是GIF
} else {//图片读取失败
}catch (FileNotFoundException e){
e.printStackTrace();
其中参数uri为GIF图片的Uri路径,frameList为解码的结果,即GIF图片中各帧的集合,里面包括各帧静态图Bitmap和延迟时间。GifFrame是保存各帧的对象,具体实现和内部属性如下
* 各帧对象
public static class GifFrame {
public B//静态图Bitmap
//图像延迟时间
public GifFrame(Bitmap im, int del) {
GifDecoder定义了三种解码状态
public static final int STATUS_OK = 0;//解码成功
public static final int STATUS_FORMAT_ERROR = 1;//图片格式错误
public static final int STATUS_OPEN_ERROR = 2;//打开图片失败
从GifDecoder的使用示例中,我们可以看到GifDecoder解码GIF图片的入口为read(InputStream is)方法,具体实现如下
//解码状态
protected Vector&GifFrame&//存放各帧对象的数组
protected int frameC//帧数
protected int[] //全局颜色列表
protected int[] //局部颜色列表
* 解码入口,读取GIF图片输入流
* @param is
public int read(InputStream is) {
if (is != null) {
readHeader();
if (!err()) {
readContents();
if (frameCount & 0) {
status = STATUS_FORMAT_ERROR;
status = STATUS_OPEN_ERROR;
is.close();
} catch (Exception e) {
e.printStackTrace();
* 初始化参数
protected void init() {
status = STATUS_OK;
frameCount = 0;
frames = new Vector&GifFrame&();
* 判断当前解码过程是否出错
protected boolean err() {
return status != STATUS_OK;
可以看到read(InputStream is)方法中体现了完整的解码流程以及状态判断,其调用的readHeader()和readContents()即为具体的GIF内部数据读取方法。下一节我们将深入readHeader()方法看看GifDecoder是如何处理GIF文件头(File Header)的
判断传入文件格式
解码之前肯定要先判断解码的对象是否为GIF图片,readHeader()中就实现了此判断过程,判断文件格式的代码部分如下
* 读取GIF 文件头、逻辑屏幕标识符、全局颜色列表
protected void readHeader() {
//根据文件头判断是否GIF图片
String id = "";
for (int i = 0; i & 6; i++) {
id += (char) read();
if (!id.toUpperCase().startsWith("GIF")) {
status = STATUS_FORMAT_ERROR;
//解析GIF逻辑屏幕标识符和全局颜色列表
* 按顺序一个一个读取输入流字节,失败则设置读取失败状态码
protected int read() {
int curByte = 0;
curByte = in.read();
} catch (Exception e) {
status = STATUS_FORMAT_ERROR;
return curB
怎么理解这段代码呢?前文我们提到文件头(File Header)中包含了GIF的文件署名和版本号,共占6个字节(见下图),其中前3个字节存放的是GIF的文件署名,即‘G’、‘I’、‘F’三个字符,那么这段代码就很好理解了,就是根据读取出来的文件头字符串开头是否为‘GIF’来判断此文件格式符不符合要求
文件头(File Header)
读取GIF大小、颜色深度等全局属性
readHeader()中还有一部分代码,如下
protected boolean gctF//是否使用了全局颜色列表
protected int bgI //背景颜色索引
protected int gctS //全局颜色列表大小
protected int bgC //背景颜色
protected void readHeader() {
//根据文件头判断是否GIF图片
//读取GIF逻辑屏幕标识符
readLSD();
//读取全局颜色列表
if (gctFlag && !err()) {
gct = readColorTable(gctSize);
bgColor = gct[bgIndex];//根据索引在全局颜色列表拿到背景颜色
其对应的正是GIF数据流(GIF Data Stream)的前两部分逻辑屏幕标识符(Logical Screen Descriptor)与全局颜色列表(Global Color Table)的解析,也就是说readHeader()完成了读取GIF图像数据前所有全局属性、配置信息的读取与解析。接下来我们先看readLSD()方法是如何解析逻辑屏幕标识符(Logical Screen Descriptor)(见下图)的
逻辑屏幕标识符(Logical Screen Descriptor)
//完整的GIF图像宽度
//完整的GIF图像高度
protected int pixelA //像素宽高比(Pixel Aspect Radio)
* 读取逻辑屏幕标识符(Logical Screen Descriptor)与全局颜色列表(Global Color Table)
protected void readLSD() {
//获取GIF图像宽高
width = readShort();
height = readShort();
* 解析全局颜色列表(Global Color Table)的配置信息
* 配置信息占一个字节,具体各Bit存放的数据如下
| s | pixel |
int packed = read();
gctFlag = (packed & 0x80) != 0;//判断是否有全局颜色列表(m,0x80在计算机内部表示为)
gctSize = 2 && (packed & 7);//读取全局颜色列表大小(pixel)
//读取背景颜色索引和像素宽高比(Pixel Aspect Radio)
bgIndex = read();
pixelAspect = read();
* 读取两个字节的数据
protected int readShort() {
return read() | (read() && 8);
根据readLSD()的读取结果,我们知道了此GIF图像中是否含有全局颜色列表(Global Color Table)(见下图),如果有,就调用readColorTable(int ncolors)方法获取全局颜色列表
全局颜色列表(Global Color Table)
* 读取颜色列表
* @param ncolors 列表大小,即颜色数量
protected int[] readColorTable(int ncolors) {
int nbytes = 3 *//一个颜色占3个字节(r g b 各占1字节),因此占用空间为 颜色数量*3 字节
int[] tab =
byte[] c = new byte[nbytes];
int n = 0;
n = in.read(c);
} catch (Exception e) {
e.printStackTrace();
if (n & nbytes) {
status = STATUS_FORMAT_ERROR;
} else {//开始解析颜色列表
tab = new int[256];//设置最大尺寸避免边界检查
int i = 0;
int j = 0;
while (i & ncolors) {
int r = ((int) c[j++]) & 0
int g = ((int) c[j++]) & 0
int b = ((int) c[j++]) & 0
tab[i++] = 0xff000000 | (r && 16) | (g && 8) |
至此readHeader()我们就分析完了,接下来分析readContents()方法是如何提取GIF图像的各帧图片的
提取各帧图片
我们先直接观察readContents()方法内部是如何运作的
* 读取图像块内容
protected void readContents() {
boolean done =
while (!(done || err())) {
int code = read();
switch (code) {
//图象标识符(Image Descriptor)开始
case 0x2C:
readImage();
//扩展块开始
case 0x21: //扩展块标识,固定值0x21
code = read();
switch (code) {
case 0xf9: //图形控制扩展块标识(Graphic Control Label),固定值0xf9
readGraphicControlExt();
case 0xff: //应用程序扩展块标识(Application Extension Label),固定值0xFF
readBlock();
String app = "";
for (int i = 0; i & 11; i++) {
app += (char) block[i];
if (app.equals("NETSCAPE2.0")) {
readNetscapeExt();
skip(); // don't care
default: //其他扩展都选择跳过
case 0x3b://标识GIF文件结束,固定值0x3B
case 0x00: //可能会出现的坏字节,可根据需要在此处编写坏字节分析等相关内容
status = STATUS_FORMAT_ERROR;
readContents()的核心流程就是根据块的标识来判断当前解码的位置,调用相应的方法对数据块进行解码。如果GIF版本为89a,则数据块中可能含有扩展块(可选)。其中图像延迟时间存放在图形控制扩展(Graphic Control Extension)中,因此我们重点分析如何读取图形控制扩展(Graphic Control Extension)(见下图),其他扩展块解码大家可以对照着代码注释和GIF结构的相关知识自行研究,这里就不多赘述了
图形控制扩展(Graphic Control Extension)
解码图形控制扩展(Graphic Control Extension)的方法为readGraphicControlExt(),有了上图对各字节的说明其代码也就很容易理解了,如下
* 读取图形控制扩展块
protected void readGraphicControlExt() {
read();//按读取顺序,此处为块大小
int packed = read();//读取处置方法、用户输入标志等
dispose = (packed & 0x1c) && 2; //从packed中解析出处置方法(Disposal Method)
if (dispose == 0) {
dispose = 1; //elect to keep old image if discretionary
transparency = (packed & 1) != 0;//从packed中解析出透明色标志
delay = readShort() * 10;//读取延迟时间(毫秒)
transIndex = read();//读取透明色索引
read();//按读取顺序,此处为标识块终结(Block Terminator)
GIF中可能含有多个图像块,图像块包含图象标识符(Image Descriptor)(见下图)、局部颜色列表(Local Color Table)(根据局部颜色列表标志确定是否存在)以及基于颜色列表的图象数据(Table-Based Image Data)
图象标识符(Image Descriptor)
readContents()方法中遍历了所有图像块,并调用readImage()进行解码,代码及注释如下
protected boolean lctF//局部颜色列表标志(Local Color Table Flag)
protect//交织标志(Interlace Flag)
protected int lctS//局部颜色列表大小(Size of Local Color Table)
* 按顺序读取图像块数据:
* 图象标识符(Image Descriptor)
* 局部颜色列表(Local Color Table)(有的话)
* 基于颜色列表的图象数据(Table-Based Image Data)
protected void readImage() {
* 开始读取图象标识符(Image Descriptor)
ix = readShort();//x方向偏移量
iy = readShort();//y方向偏移量
iw = readShort();//图像宽度
ih = readShort();//图像高度
int packed = read();
lctFlag = (packed & 0x80) != 0;//局部颜色列表标志(Local Color Table Flag)
interlace = (packed & 0x40) != 0;//交织标志(Interlace Flag)
// 3 - sort flag
// 4-5 - reserved
lctSize = 2 && (packed & 7);//局部颜色列表大小(Size of Local Color Table)
* 开始读取局部颜色列表(Local Color Table)
if (lctFlag) {
lct = readColorTable(lctSize);//解码局部颜色列表
act =//若有局部颜色列表,则图象数据是基于局部颜色列表的
act = //否则都以全局颜色列表为准
if (bgIndex == transIndex) {
bgColor = 0;
int save = 0;
if (transparency) {
save = act[transIndex];//保存透明色索引位置原来的颜色
act[transIndex] = 0;//根据索引位置设置透明颜色
if (act == null) {
status = STATUS_FORMAT_ERROR;//若没有颜色列表可用,则解码出错
if (err()) {
* 开始解码图像数据
decodeImageData();
if (err()) {
frameCount++;
image = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
setPixels(); //将像素数据转换为图像Bitmap
frames.addElement(new GifFrame(image, delay));//添加到帧图集合
if (transparency) {
act[transIndex] =//重置回原来的颜色
resetFrame();
readImage()中分三步进行:读取图象标识符(Image Descriptor)、读取局部颜色列表(Local Color Table)和解码图像数据。其中图像数据是如何解码并转换成Bitmap图像因为太复杂这里就不详细展开描述了,以后可能会专门写个番外篇进行分析,当然小伙伴们也可以自行阅读分析这部分源码:decodeImageData()、setPixels()
至此 GifDecoder就基本分析完了,如果有讲解不到位的地方欢迎大家留言指正。如果大家看了感觉还不错麻烦点个赞,你们的支持是我最大的动力~
博客已搬至掘金:https://juejin.im/user/5aaa,以后都将在那首发,简书这边就随缘更新了,谢谢大家支持~
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金相信有很多朋友...
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金 相信有很多...
37 视频滤镜 在配置编译FFmpeg时可以通过--disable-filters来禁止所有滤镜的编译。也可以配置编译脚本来输出所有包含进编译的滤镜信息。 下面是当前可用的视频滤镜介绍。 alphaextract 把输入视频作为灰度视频来提取透明通道,它通常和alphame...
发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注
09:45字数 61697阅读 3316评论 2喜欢 85 用到的组件 1、通过CocoaPods安装 项目名称 项目信息 AFNetworking 网络请求组件 FM...
用到的组件1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SDWebImage多个缩略图缓存组件 UICKeyChainStore存放用户账号密码组件 Reachability监测网络状态 DateTools友好...
今天,5月第1天,假日中。 今天的假日,于我,只是概念上的假日,没有休息。于二医院为患者治疗的医生,也是概念上的假日,没有休息。 自4月21日始,住院治疗我的颈椎。在住院治疗过程中,我接触、认识了很多医生。对医生的工作有了更直接的了解。 在这里,我不想说医生的治疗技术,我只...
创业成功,主要靠什么? 每一份商业计划书里,都会有:商业模式、产品、创始人背景、技术....... blablabla说一大堆。 但有一样,却少有人提。这就是“运气”。 创业本就是个赌概率的勾当。要不为什么投资叫“VC” 啊? 尤其在创新型的创业里,主导的规律应该是量子力学...
凌晨一点一刻 我的肚子叫了一声 “咕——” 我翻了个身,压到空气 又叫了一声 “咕噜——” 昂面躺着 摸摸瘪瘪的肚子和突出的肋骨 “咕——咕咕——” 我按亮手机 眯缝眼盯着白色屏幕 “咕咕——咕噜——” 空荡荡的肚子里有回声 一阵一阵又一阵 翻身趴着,按住肚子 睡意让屏幕模...
玫瑰花全世界的人都知道这种花,它是情侣之间的礼物代表作。玫瑰花代表着美丽和爱情,它属于蔷薇的一种,它的艳丽像女人一般。玫瑰花根据花色分:红玫瑰、黄玫瑰、紫玫瑰、黑玫瑰、白玫瑰、蓝玫瑰、橘红色玫瑰等等。每一种玫瑰都代表着自己的花语。虽然优逸生活网预计在2017年3月份试运营,...
1.2每个对象都有一个接口 第一个面向对象语言Simula-67.他在程序中使用基本关键字class来引入新的类型。这个语言是为了开发诸如经典的“银行出纳员问题”(bank teller problem)这样的仿真程序而创建的。 class的由来:在程序执行期间具有不同的状...1.2.3.4.5.6.
您可以选择一种方式赞助本站请教各位高手,如何打开指定数据库的指定帧结构集_百度知道
请教各位高手,如何打开指定数据库的指定帧结构集
我有更好的答案
设置checkbox的tag值为它对应的列名,例如chkA.Tag = &Column1&;然后点button时:string
strSql = & select &;int
count = 0;
//选中的checkbox个数if(chkA.checked){sql+=chkA.Tag.ToString() + &,&;count
++;}.....其它同理if(count == 0){//请选择列}else{strSql = strSql.SubString(0,strSql.Length-1);strSql += & from table&; }本回答由提问者推荐答案纠错 | 评论 0 0lixian45 采纳率:18% 擅长: 暂未定制其他回答在button的click事件里判断每个checkbox的选中状态,然后拼接出来sql语句。如:string sql=&select &;if(chkA.checked){sql+=&columnA &;}if(chkB.checked){sql+=&,columnB&;}...sql += & from table1&;//下面执行sql去检索对应的数据就行了
为您推荐:
其他类似问题
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。}

我要回帖

更多关于 怎么才能让蜗牛出来 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信