鸿蒙里ForEach的第三个参数必须要写,还要写对!以购物车为例

一、ForEach的使用API

ForEach()里面有三个参数,但我们一般只写前两个参数,第三个参数是可选的。但如果涉及到数组的删除操作时,非常建议写上第三个参数,否则删除时很可能会出现Bug!

原因:列表渲染完毕后,在数组的中段进行删除操作,如果不写第三个参数来给数组生成唯一的标识,在删除中间的列表项后,后面的数组的下标会变化,ForEach组件会认为后面的那些数据是新数据,从而会引起后面数据的再次渲染。这会导致例如后面组件的选中状态等发生改变。

解决方法:在ForEach里面增加第三个回调函数来给每一项加上唯一标识,这样就可以避免后面数组再次重新渲染,达到不破坏后面数组状态的目的。

ForEach(arr: Array<any>, itemGenerator(item: any, index: number) => void, keyGenerator?: (item: any, index: number) => string)

参数名类型必填说明
arrArray<any>

数据源,为Array类型的数组。

说明:

- 可以设置为空数组,此时不会创建子组件。

- 可以设置返回值为数组类型的函数,例如arr.slice(1, 3),但设置的函数不应改变包括数组本身在内的任何状态变量,例如不应使用Array.splice(),Array.sort()或Array.reverse()这些会改变原数组的函数。

itemGenerator(item: any, index: number) => void

组件生成函数。

- 为数组中的每个元素创建对应的组件。

- item参数(可选):arr数组中的数据项。

- index参数(可选):arr数组中的数据项索引。

说明:

- 组件的类型必须是ForEach的父容器所允许的。例如,ListItem组件要求ForEach的父容器组件必须为List组件。

keyGenerator(item: any, index: number) => string

键值生成函数。

- 为数据源arr的每个数组项生成唯一且持久的键值。函数返回值为开发者自定义的键值生成规则。

- item参数(可选):arr数组中的数据项。

- index参数(可选):arr数组中的数据项索引。

说明:

- 如果函数缺省,框架默认的键值生成函数为(item: T, index: number) => { return index + '__' + JSON.stringify(item); }

- 键值生成函数不应改变任何组件状态。

说明

  • ForEach的itemGenerator函数可以包含if/else条件渲染逻辑。另外,也可以在if/else条件渲染语句中使用ForEach组件。
  • 在初始化渲染时,ForEach会加载数据源的所有数据,并为每个数据项创建对应的组件,然后将其挂载到渲染树上。如果数据源非常大或有特定的性能需求,建议使用LazyForEach组件。最佳实践请参考使用懒加载优化性能

二、水果购物车案例

效果图:

案例代码:

下载文章顶部图片资源,解压到media里面,将下方代码复制到pages/fruitPage.ets里保存打开预览器查看效果。

interface IFruit {
  id: number
  name?: string
  img: ResourceStr
  price: number
  count: number
}

@Observed
class Fruit {
  id: number
  name?: string
  img: ResourceStr
  price: number
  count: number

  constructor(id: number, name: string, img: ResourceStr, price: number, count: number) {
    this.id = id
    this.name = name
    this.img = img
    this.price = price
    this.count = count
  }
}

@Entry
@Component
struct fruitPage {
  menuList: string[] = ['选中', '图片', '单价', '个数', '小计', '操作']
  @State fruitList: Fruit[] = [
    new Fruit(1, '火龙果', $r('app.media.huolongg'), 6, 2),
    new Fruit(2, '榴莲', $r('app.media.liulian'), 25, 1),
    new Fruit(3, '荔枝', $r('app.media.lizhi'), 7, 1),
    new Fruit(4, '鸭梨', $r('app.media.yali'), 6, 5),
    new Fruit(5, '樱桃', $r('app.media.yingtao'), 18, 8),
  ]

  build() {
    Column() {
      Image($r('app.media.fruit'))
        .width('100%')
      Row() {
        Image($r('app.media.lizhi'))
          .width(15)
          .borderRadius('50%')
          .aspectRatio(1)
        Text(' / 购物车')
          .fontSize(12)
      }
      .width('100%')
      .padding(5)

      ShopList({ menuList: this.menuList, fruitList: this.fruitList })
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct ShopList {
  @Prop menuList: string[]
  @Link fruitList: Fruit[]
  @Provide totalPrice: number = 0
  @Provide totalCount: number = 0
  @State @Watch('toPrice') idArray: string[] = [] // 存储选中商品的id

  toPrice() {
    this.totalPrice = this.fruitList.filter(eve => this.idArray.includes(eve.id.toString()))
      .reduce((acc, cur) => acc + cur.price * cur.count, 0)
    this.totalCount = this.fruitList.filter(eve => this.idArray.includes(eve.id.toString()))
      .reduce((acc, cur) => acc + cur.count, 0)
  }

  build() {
    List() {
      // 表头
      ListItem() {
        Row() {
          ForEach(this.menuList, (item: string, index) => {
            Text(item)
              .fontSize(12)
          }, (item: string, index: number) => item)
        }
        .width('100%')
        .height(40)
        .backgroundColor('#fafafa')
        .justifyContent(FlexAlign.SpaceAround)
      }

      if (this.fruitList.length > 0) {
        ForEach(this.fruitList, (item: Fruit, index) => {
          ListItem() {
            fruitListItem({
              item: item,
              delListItem: () => {
                this.fruitList = this.fruitList.filter(eve => eve.id !== item.id)
              }
            })
          }
        }, (item: string, index: number) => item)
        ListItem() {
          TotalComp({ idArray: this.idArray })
        }
      } else {
        ListItem() {
          Row() {
            Text('🛒').fontSize(30)
            Text('空空如也').fontColor('#919398').fontSize(30)
          }
          .justifyContent(FlexAlign.Center)
          .width('100%')
          .height(40)
          .border({ width: { top: 1 }, color: { top: '#f3f3f3' } })
          .margin({ top: 10 })
        }
      }
    }
  }
}

@Component
struct fruitListItem {
  @ObjectLink item: Fruit
  delListItem = () => {
  }

  build() {
    Row() {
      Checkbox({ name: this.item.id.toString(), group: 'fruit' })
        .width(14)
        .shape(CheckBoxShape.ROUNDED_SQUARE)
        .margin({ left: 20 })
      Image(this.item.img)
        .height('100%')
        .aspectRatio(1)
        .offset({ x: 18 })
      Text(this.item.price.toString())
        .fontSize(12)
        .offset({ x: 30 })
      Counter() {
        Text(this.item.count.toString())
      }
      .scale({ x: 0.5, y: 0.7 })
      .offset({ x: 13 })
      .enableInc(true)
      .enableDec(this.item.count > 1)
      .onInc(() => {
        this.item.count++
      })
      .onDec(() => {
        this.item.count--
      })

      Text('12')
        .fontSize(12)
        .width(40)
      Button('删除')
        .width(40)
        .backgroundColor(Color.Red)
        .fontColor(Color.White)
        .borderRadius(5)
        .padding({ left: 8, right: 8 })
        .fontSize(12)
        .height(30)
        .offset({ x: -10 })
        .onClick(() => {
          this.delListItem()
        })
    }
    .width('100%')
    .height(40)
    .backgroundColor('#fafafa')
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

@Component
struct TotalComp {
  @Consume totalPrice: number
  @Consume totalCount: number
  @Link idArray: string[]

  build() {
    Row() {
      Row() {
        CheckboxGroup({ group: 'fruit' })
          .onChange((event) => {
            this.idArray = event.name
          })
          .checkboxShape(CheckBoxShape.ROUNDED_SQUARE)

        Text('全选')
          .fontSize(12)
      }
      .layoutWeight(1)

      Row({ space: 10 }) {
        Text() {
          Span('总价 : ¥')
          Span(this.totalPrice.toString()).fontSize(20).fontColor('#ff69b4')
        }

        Button(`结算(${this.totalCount})`)
          .borderRadius(3)
          .height(30)
      }
    }
    .width('100%')
    .padding(10)
    .border({ width: { bottom: 1 }, color: { bottom: '#eceff4' } })
  }
}

2.1 水果购物车的ForEach第三个参数

在上方代码92-101中就是ForEach循环语句,负责循环渲染列表项。

ForEach(this.fruitList, (item: Fruit, index) => {
          ListItem() {
            fruitListItem({
              item: item,
              delListItem: () => {
                this.fruitList = this.fruitList.filter(eve => eve.id !== item.id)
              }
            })
          }
        }, (item: string, index: number) => item)

我们着重关注这一行:

ForEach(this.fruitList, (item: Fruit, index) => {
  ListItem() {}
}, (item: string, index: number) => item)

这就是保证每一项数据都有特定的唯一标识的ForEach第三个参数,需要注意的是,在 (item: string, index: number) => item 中,里面的index没有使用到,可省略,改成 (item: string) => item 一样能实现效果。

三、商品购物车案例

效果图:

代码:

ets/data/GoodData.ets

export interface Good {
  id: string // id
  wname: string // 名称
  jdPrice: number // 会员价
  imageurl: string // 图片
  jdMainPrice: number // 原价
}

@Observed
export class CartGood {
  id: string // id
  good: Good // 商品
  count: number // 个数

  constructor(good: Good, count: number = 1) {
    this.id = good.id
    this.good = good
    this.count = count
  }
}


export const goodList: CartGood[] = [
  new CartGood({
    id: '16756',
    wname: '惠润 绿野芳香洗发露 600ml',
    jdPrice: 35.00,
    imageurl: 'https://m.360buyimg.com/mobilecms/s558x558_jfs/t1/188208/35/38739/41823/6515995cF15cdb08a/cf82ab82e3e36eb7.jpg!q50.jpg.webp',
    jdMainPrice: 45.00,
  }),
  new CartGood({
    id: '16758',
    wname: '潘婷 高保湿深水泡弹 12ml*8盒',
    jdPrice: 59.00,
    imageurl: 'https://m.360buyimg.com/mobilecms/s558x558_jfs/t1/246382/22/1792/26433/6596a0ccFea974bdb/f4961f6aeb7d998f.png!q50.jpg.webp',
    jdMainPrice: 78.00,
  }),
  new CartGood({
    id: '29714',
    wname: '得力 折叠中国象棋套装 原木色 中号',
    jdPrice: 28.90,
    imageurl: 'https://m.360buyimg.com/mobilecms/s558x558_jfs/t1/182908/16/25089/133946/62959bf1E13ddee79/f8c0cd4baaa57db8.jpg!q50.jpg.webp',
    jdMainPrice: 36.90,
  }),
  new CartGood({
    id: '854',
    wname: '南孚 5号碱性电池 30粒',
    jdPrice: 56.00,
    imageurl: 'https://m.360buyimg.com/mobilecms/s558x558_jfs/t1/199998/27/33294/68995/642aceebF857a36e0/994c63e2c6fa0bbb.png!q50.jpg.webp',
    jdMainPrice: 65.90,
  }),
  new CartGood(
    {
      id: '34849',
      wname: '洁丽雅  新疆棉方巾4条装  35*35cm 55g/条',
      jdPrice: 22.90,
      imageurl: 'https://m.360buyimg.com/mobilecms/s558x558_jfs/t1/245460/14/1920/91732/659534b6Fa05d822a/7a92fea15856ee28.png!q50.jpg.webp',
      jdMainPrice: 29.90,
    },
  ), new CartGood(
  {
    id: '3484',
    wname: '洁柔手帕纸Lotion',
    jdPrice: 12.40,
    imageurl: 'https://img14.360buyimg.com/mobilecms/s360x360_jfs/t1/141643/29/1675/581709/5ef85e48Efc16bf7d/1a42445a7d7d55c4.jpg!q70.dpg.webp',
    jdMainPrice: 29.90,
  },
)
]

ets/pages/Index.ets

import { CartGood, goodList } from '../data/GoodData'

const MAIN_RED: string = '#f4304b'

const LIGHT_GRAY: string = '#f5f5f5'

const DEEP_GRAY: string = '#bebebe'

@Entry
@Component
struct Main {
  @State list: CartGood[] = goodList
  @State @Watch('filterGoods') idArray: string[] = [] // 存储选中商品的id
  @Provide totalPrice: number = 0 // 总价
  @Provide totalCount: number = 0 // 总数

  filterGoods() {
    this.totalPrice = this.list.filter(item => this.idArray.includes(item.id))
      .reduce((sum: number, item: CartGood) => sum + item.good.jdPrice * item.count, 0)
    this.totalCount = this.list.filter(item => this.idArray.includes(item.id))
      .reduce((sum: number, item: CartGood) => sum + item.count, 0)

  }

  build() {
    Column() {
      // 标题
      TitleCom({ count: this.list.length })
      // 内容
      ContentCom({ list: this.list })

      // 支付
      PayCom({ idArray: this.idArray })
    }
    .height('100%')
    .backgroundColor(LIGHT_GRAY)
  }
}

// 内容区域
@Component
struct ContentCom {
  @Link list: CartGood[]

  build() {
    Scroll() {
      Column() {
        // 支付
        FreightCom()
          .margin(10)
        // 商品列表
        Column({ space: 10 }) {
          ListTitleCom()
          // 自营区域
          List() {
            ForEach(this.list, (item: CartGood, index: number) => {
              ListItem() {
                GoodsListItem({ item: item })
              }
              .swipeAction({ end: this.delItem(item.id) })
            }, (item: CartGood, index: number) => {
              return item.id
            })
          }
          .divider({
            strokeWidth: .5,
            startMargin: 10,
            endMargin: 10,
            color: DEEP_GRAY
          })
        }
        .backgroundColor(Color.White)
        .margin({ left: 10, right: 10 })

        // 空车 商品为空时显示
        if (this.list.length == 0) {
          EmptyCom()
        }
      }
    }
    .align(Alignment.Top)
    .padding({ bottom: 10 })
    .edgeEffect(EdgeEffect.Spring)
    .layoutWeight(1)
  }

  // 列表删除按钮样式
  @Builder
  delItem(id: string) {
    Text('删除')
      .width(60)
      .height('100%')
      .backgroundColor(Color.Red)
      .fontColor('#fff')
      .textAlign(TextAlign.Center)
      .onClick(() => {
        this.list = this.list.filter(item => item.id != id)
      })
  }
}

// 标题(显示商品种类)
@Component
struct TitleCom {
  @Prop count: number = 0

  build() {
    Row() {
      // 文字
      Stack({ alignContent: Alignment.Bottom }) {
        Text(`购物车(${this.count})`)
          .height('100%')
        Text('')
          .width(25)
          .height(2)
          .linearGradient({ angle: 90, colors: [[MAIN_RED, 0], [Color.White, 1]] })
      }
      .height('100%')

      // 地址
      Row() {
        Image($r('app.media.ic_yhd_location'))
          .width(15)
          .fillColor(DEEP_GRAY)
        Text('北京市昌平区建材城西路')
          .fontSize(12)
          .fontColor(DEEP_GRAY)
      }
      .height(20)
      .padding({ left: 5, right: 5 })
      .borderRadius(10)
      .backgroundColor(LIGHT_GRAY)

      // 编辑
      Text('编辑')
    }
    .padding({ left: 20, right: 20 })
    .width('100%')
    .height(40)
    .justifyContent(FlexAlign.SpaceBetween)
    .backgroundColor(Color.White)
  }
}

// 运费(地址下方)
@Component
struct FreightCom {
  // 默认 69 可以由外部传入
  minPrice: number = 69.00
  @Consume totalPrice: number

  build() {
    Column() {
      // 运费不够 提示
      if(this.minPrice > this.totalPrice){
        Row() {
          Row({ space: 5 }) {
            // 凑单免运费
            Text() {
              Span('凑单')
              Span('免运费')
                .fontColor(MAIN_RED)
            }
            .fontSize(13)
            .fontFamily('medium')

            // 分割线
            Divider()
              .vertical(true)
              .height(8)
              .color(DEEP_GRAY)
              .strokeWidth(1)

            // 运费信息

            Row() {
              Text() {
                Span('还需凑钱 ')
                Span(`¥${(this.minPrice - this.totalPrice).toFixed(2)}`)
                  .fontColor(MAIN_RED)
                Span('可免运费')
              }
              .fontSize(13)

              Image($r('app.media.ic_yhd_order_info'))
                .width(15)
            }
          }

          // 按钮
          Button() {
            Row() {
              Text('去凑单')
                .fontColor(Color.White)
                .fontSize(12)
              Image($r('app.media.ic_public_arrow_right'))
                .height(14)
                .width(10)
                .fillColor(Color.White)
            }
            .backgroundColor(MAIN_RED)
            .borderRadius(20)
            .padding({
              left: 10,
              top: 3,
              bottom: 3,
              right: 2
            })
          }
        }
        .width('100%')
      } else {
        // 运费足够 提示
        Row({ space: 5 }) {
          Text('运费')
            .backgroundColor(MAIN_RED)
            .fontSize(12)
            .fontColor(Color.White)
            .padding(2)
            .borderRadius(3)
          Divider()
            .vertical(true)
            .height(12)
            .strokeWidth(2)
          Text('已免运费')
            .fontSize(12)
            .fontColor(Color.Gray)
          Image('/common/day08-10/yhd/ic_yhd_order_info.png')
            .width(15)

        }
      }
    }
    .borderRadius(5)
    .height(30)
    .padding({ left: 8, right: 8 })
    .linearGradient({ colors: [['#ffe8ea', 0], [Color.White, 1]] })
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}

// 支付(最下方组件)
@Component
struct PayCom {
  @Link idArray: string[]
  @Consume totalPrice: number
  @Consume totalCount: number

  build() {
    Row() {
      Row() {
        CheckboxGroup({
          group: 'cart'
        })
          .selectedColor(MAIN_RED)
          .onChange((event) => {
            this.idArray = event.name
          })
        Text('全选')
          .fontSize(12)
      }

      Row() {
        Text('合计:')
          .fontSize(14)
        PriceCom({
          fontColor: Color.Black,
          price: this.totalPrice
        })
        Button(`入会结算(${this.totalCount})`)
          .fontColor('#ffe3cc')
          .backgroundColor(Color.Black)
          .fontSize(14)
          .margin({ left: 5 })
      }

    }
    .justifyContent(FlexAlign.SpaceBetween)
    .padding({ left: 10, right: 10 })
    .height(48)
    .width('100%')
    .backgroundColor(Color.White)
  }
}

// 价格(根据传入的价格渲染数字)
@Component
struct PriceCom {
  @Prop price: number = 0
  fontColor: ResourceColor = MAIN_RED
  discard: boolean = false

  getSplicePrice() {
    return this.price.toFixed(2)
      .split('.')
  }

  build() {
    Text() {
      Span('¥')
        .fontSize(12)
      Span(this.getSplicePrice()[0]
        .toString())
        .fontSize(this.discard ? 12 : 16)
        .fontWeight(600)
      Span('.')
      Span(this.getSplicePrice()[1] == undefined ? '00' : this.getSplicePrice()[1])
        .fontSize(12)
    }
    .fontColor(this.fontColor)
    .decoration({ type: this.discard ? TextDecorationType.LineThrough : TextDecorationType.None })
  }
}

// 空车:购物车为空显示
@Component
struct EmptyCom {
  build() {
    Column({ space: 20 }) {
      Image($r('app.media.ic_yhd_cart_empty'))
        .width(90)
      Text('购物车竟然是空的~')
        .fontSize(14)
        .fontColor(Color.Gray)
    }
    .width('100%')
    .backgroundColor(Color.White)
    .padding(50)

  }
}

// 列表区域标题:装饰用
@Component
struct ListTitleCom {
  build() {
    Row({ space: 5 }) {
      Image($r('app.media.ic_yhd_logo'))
        .width(12)
      Text('自营')
        .fontWeight(600)
        .fontSize(15)
      Divider()
        .vertical(true)
        .height(10)
        .strokeWidth(2)
      Text('1号会员店提供服务')
        .fontColor(DEEP_GRAY)
        .fontSize(12)
    }
    .alignSelf(ItemAlign.Start)
    .padding({ left: 15, top: 10 })
  }
}

// 列表项:
@Component
struct GoodsListItem {
  @ObjectLink item: CartGood

  build() {
    Row({ space: 10 }) {
      // 左
      Checkbox({
        group: 'cart',
        name: this.item.id
      })
        .shape(CheckBoxShape.CIRCLE)
        .selectedColor(MAIN_RED)


      // 右
      Row({ space: 8 }) {
        // 商品图片
        Image(this.item.good.imageurl)
          .width(90)
          .padding(5)
          .border({ width: .5, color: DEEP_GRAY })
          .borderRadius(10)
        //   信息
        Column() {
          // 标题
          Text(this.item.good.wname)
            .maxLines(2)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
            .fontSize(14)
            .fontWeight(400)
            .width('100%')
          // 价格 + 数量
          Row() {
            Column() {
              // 左
              Row() {
                Image($r('app.media.ic_yhd_hyj'))
                  .width(35)
                PriceCom({ price: this.item.good.jdPrice })
              }

              PriceCom({ discard: true, fontColor: DEEP_GRAY, price: this.item.good.jdMainPrice })
            }
            .alignItems(HorizontalAlign.Start)

            Blank()

            // 个数 Counter 内置组件
            Counter() {
              Text(this.item.count.toString())
            }
            .enableInc(true)
            .enableDec(this.item.count > 1)
            .scale({ x: .8, y: .8 })
            .onInc(() => {
              this.item.count += 1
            })
            .onDec(() => {
              this.item.count -= 1
            })
          }
          .justifyContent(FlexAlign.SpaceBetween)
          .width('100%')
        }
        .height(90)
        .justifyContent(FlexAlign.SpaceBetween)
        .layoutWeight(1)
      }
      .layoutWeight(1)
    }
    .padding({ left: 10, top: 10, bottom: 10 })

  }
}

3.1 商品购物车的ForEach第三个参数

在文件 ets/pages/Indexets 中代码第6 - 63行就是ForEach循环语句,负责循环渲染列表项。

 ForEach(this.list, (item: CartGood, index: number) => {
              ListItem() {
                GoodsListItem({ item: item })
              }
              .swipeAction({ end: this.delItem(item.id) })
            }, (item: CartGood, index: number) => {
              return item.id
            })
 

我们着重关注这一行:

(item: CartGood, index: number) => {
              return item.id
            }

在这个案例中,第三个参数item类型不是string类型了,而是复杂数据类型(CartGood),在这里即使index没有用到,也不可以删除。否则会出现Bug!例如全部选中删除中间项,后面的数组会重新渲染并且选中状态就没了。

如果将item类型改成string类型,下方返回item,也可以正常实现效果,但是index依旧不可以省略!如果省略index,那么每次点击item的删除,传过去的则不是id,而是数组下标!这个就很迷。。。想删张三结果把李四删了。。。

需要注意的点:

对比两个案例,都用到了ForEach的第三个参数来生成唯一标识。但是第二个商品购物车案例有点蹊跷,虽然index没有用到,但是不可省略。具体原因不去深究了,后面会随着API更新迭代掉。

结论:第三个参数最好还是写上,把item和index都写上吧~

二编:Bug解决了~

Q1. 第三个回调参数里item是什么东西,为什么写类型和写string一样可以实现效果?

A1:以水果购物车为例:

ForEach(this.fruitList, (item: Fruit, index) => {
  ListItem() {
    fruitListItem({
      item: item,
      delListItem: () => {
        this.fruitList = this.fruitList.filter(eve => eve.id !== item.id)
      }
    })
  }
}, (item: string, index) => {
  console.log(item,'item')
  return item
})

在上面的代码里,第三个回调参数里打印了item,我们可以看到输出结果为:

这说明item为 Fruit 对象,那我们把输出语句改成 console.log(JSON.stringify(item)) 再来试试呢,结果发现输出为:

这验证了我们的说法。

结论

过程省略,我相信各位对结论比较感兴趣,因为我的这个步骤写的不是特别好,所以我们直接上结论:

结论:当涉及到了数组的删除时,为了避免后面的数据重新渲染,第三个回调函数的参数里,item类型最好与第二个回调函数里的对应(虽然写string也可以),index必须写,即使没有用到,因为这两个是一 一对应的,否则删除时是以数据在数组中的下标来删除的。第三个回调函数的参数里item的类型最好和第二个回调函数的item的类型相同。

ForEach(this.fruitList, (item: Fruit, index) => {
  ListItem() {
    fruitListItem({
      item: item,
      delListItem: () => {
        this.fruitList = this.fruitList.filter(eve => eve.id !== item.id)
      }
    })
  }
}, (item: Fruit, index) => {
  return item.id.toString()
})

第二个回调函数里的index如果没有用到则可以省略哈~

如果第三个回调函数的item执意要写item为string类型,则生成的键值为 index_[object object] ,应该是这样子的,这块还没有验证过。不过可以确定的是,键值已经被我们自定义赋值了,删除数据也不会更改后面数据的键值从而导致刷新了,问题解决。

下载方式:https://pan.quark.cn/s/a4b39357ea24 布线问题(分支限界算法)是计算机科学和电子工程领域中一个广为人知的议题,它主要探讨如何在印刷电路板上定位两个节点间最短的连接路径。 在这一议题中,电路板被构建为一个包含 n×m 个方格的矩阵,每个方格能够被界定为可通行或不可通行,其核心任务是定位从初始点到最终点的最短路径。 分支限界算法是处理布线问题的一种常用策略。 该算法与回溯法有相似之处,但存在差异,分支限界法仅需获取满足约束条件的一个最优路径,并按照广度优先或最小成本优先的原则来探索解空间树。 树 T 被构建为子集树或排列树,在探索过程中,每个节点仅被赋予一次成为扩展节点的机会,且会一次性生成其全部子节点。 针对布线问题的解决,队列式分支限界法可以被采用。 从起始位置 a 出发,将其设定为首个扩展节点,并将与该扩展节点相邻且可通行的方格加入至活跃节点队列中,将这些方格标记为 1,即从起始方格 a 到这些方格的距离为 1。 随后,从活跃节点队列中提取队首节点作为下一个扩展节点,并将与当前扩展节点相邻且未标记的方格标记为 2,随后将这些方格存入活跃节点队列。 这一过程将持续进行,直至算法探测到目标方格 b 或活跃节点队列为空。 在实现上述算法时,必须定义一个类 Position 来表征电路板上方格的位置,其成员 row 和 col 分别指示方格所在的行和列。 在方格位置上,布线能够沿右、下、左、上四个方向展开。 这四个方向的移动分别被记为 0、1、2、3。 下述表格中,offset[i].row 和 offset[i].col(i=0,1,2,3)分别提供了沿这四个方向前进 1 步相对于当前方格的相对位移。 在 Java 编程语言中,可以使用二维数组...
源码来自:https://pan.quark.cn/s/a4b39357ea24 在VC++开发过程中,对话框(CDialog)作为典型的用户界面组件,承担着与用户进行信息交互的重要角色。 在VS2008SP1的开发环境中,常常需要满足为对话框配置个性化背景图片的需求,以此来优化用户的操作体验。 本案将系统性地阐述在CDialog框架下如何达成这一功能。 首先,需要在资源设计工具中构建一个新的对话框资源。 具体操作是在Visual Studio平台中,进入资源视图(Resource View)界面,定位到对话框(Dialog)分支,通过右键选择“插入对话框”(Insert Dialog)选项。 完成对话框内控件的布局设计后,对对话框资源进行保存。 随后,将着手进行背景图片的载入工作。 通常有两种主要的技术路径:1. **运用位图控件(CStatic)**:在对话框界面中嵌入一个CStatic控件,并将其属性设置为BST_OWNERDRAW,从而具备自主控制绘制过程的权限。 在对话框的类定义中,需要重OnPaint()函数,负责调用图片资源并借助CDC对象将其渲染到对话框表面。 此外,必须合理处理WM_CTLCOLORSTATIC消息,确保背景图片的展示不会受到其他界面元素的干扰。 ```cppvoid CMyDialog::OnPaint(){ CPaintDC dc(this); // 生成设备上下文对象 CBitmap bitmap; bitmap.LoadBitmap(IDC_BITMAP_BACKGROUND); // 获取背景图片资源 CDC memDC; memDC.CreateCompatibleDC(&dc); CBitmap* pOldBitmap = m...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值