Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

单个view-model如何显示两列表? #62

Open
ufbycd opened this issue Sep 29, 2024 · 16 comments
Open

单个view-model如何显示两列表? #62

ufbycd opened this issue Sep 29, 2024 · 16 comments

Comments

@ufbycd
Copy link

ufbycd commented Sep 29, 2024

两个列表相互关联,所以让单个view-model显示两列表。我按如下实现了view-model,初始化时两个表示的内容显示正确,但列表内容修改后,列表在显示上没有更新。什么问题?

<dialog x="c" y="m" w="50%" h="80%" v-model="assay_selector">
    <dialog_title name="title" x="0" y="0" w="100%" h="40" tr_text="选择检测项目" />
    <dialog_client x="0" y="40" w="100%" h="-100" children_layout="default(r=1,c=2,s=10)">
        <list_view item_height="50">
            <label x="0" y="0" w="100%" h="50" style="table_title" tr_text="检测项目" />

            <scroll_view name="names" x="0" y="50" w="100%" h="-50">
                <list_item v-for="{names}" children_layout="default(r=1,c=1)"
                    v-on:click="{select_name, Args=fscript?index=index, IsContinue=true}">
                    <property name="v-data:style">
                <![CDATA[ {(index == selected_name_index) ? "selected" : "default"} ]]>
                    </property>
                    <label v-data:text="{item}" />
                </list_item>
            </scroll_view>

            <scroll_bar_m x="right" y="50" w="9" h="-50" value="0" />
        </list_view>

        <list_view item_height="50">
            <label x="0" y="0" w="100%" h="50" style="table_title" tr_text="批次" />

            <scroll_view name="lots" x="0" y="50" w="100%" h="-50">
                <list_item v-for="{lots}" children_layout="default(r=1,c=1)"
                    v-on:click="{select_lot, Args=fscript?index=index, IsContinue=true}">
                    <property name="v-data:style">
            <![CDATA[ {(index == selected_lot_index) ? "selected" : "default"} ]]>
                    </property>
                    <label v-data:text="{item}" />
                </list_item>
            </scroll_view>

            <scroll_bar_m x="right" y="50" w="9" h="-50" value="0" />
        </list_view>
    </dialog_client>

    <button name="ok" x="40" y="bottom:10" w="200" h="40" tr_text="确定" v-on:click="{confirm}" />
    <button name="cancel" x="right:40" y="bottom:10" w="200" h="40" tr_text="取消" on:click="close()" />
</dialog>
#include "assay_selector_vm.h"
#include "repository/assay_repository.h"
#include "common/common.h"
#include "pubsub.h"

#include "mvvm/base/view_model_array.h"
#include "mvvm/base/navigator.h"
#include "mvvm/base/utils.h"

typedef struct assay_selector_vm {
  view_model_array_t view_model_array;

  tk_object_t *names;
  tk_object_t *lots;
  int selected_name_index;
  int selected_lot_index;
} assay_selector_vm_t;

static assay_selector_vm_t* _cast(tk_object_t* obj);

static ret_t _get_prop(tk_object_t* obj, const char* name, value_t* v) {
  assay_selector_vm_t* asvm = _cast(obj);
  return_value_if_fail(asvm != NULL, RET_BAD_PARAMS);
  ret_t ret = RET_NOT_FOUND;

  log_debug("%s: %s\n", __func__, name);
  if(view_model_array_default_get_prop(VIEW_MODEL(obj), name, v) == RET_OK) {
    ret = RET_OK;
  }

  else if (tk_str_eq(name, "names")) {
    value_set_object(v, asvm->names);
    ret = RET_OK;
  } else if(tk_str_eq(name, "lots")) {
      value_set_object(v, asvm->lots);
      ret = RET_OK;
  } else if(tk_str_eq(name, "selected_name_index")) {
      value_set_int(v, asvm->selected_name_index);
      ret = RET_OK;
  } else if(tk_str_eq(name, "selected_lot_index")) {
      value_set_int(v, asvm->selected_lot_index);
      ret = RET_OK;
  }

  // names.[<index>]
  else if(tk_str_start_with(name, "names.")) {
      ret = tk_object_get_prop(asvm->names, name + 6, v);
  }

  // lots.[<index>]
  else if(tk_str_start_with(name, "lots.")) {
      ret = tk_object_get_prop(asvm->lots, name + 5, v);
  }

  return ret;
}

static ret_t _set_prop(tk_object_t* obj, const char* name, const value_t* v) {
    return RET_NOT_IMPL;
}

static ret_t _remove_prop(tk_object_t* obj, const char* name) {
    return RET_NOT_IMPL;
}

static bool_t _can_exec(tk_object_t* obj, const char* name, const char* args) {
  assay_selector_vm_t* asvm = _cast(obj);
  return_value_if_fail(asvm != NULL, false);
  bool_t b = false;

  if(tk_str_eq(name, "select_name")) {
      b = true;
  } else if(tk_str_eq(name, "select_lot")) {
      b = true;
  } else if(tk_str_eq(name, "confirm")) {
    uint32_t names_len = tk_object_get_prop_uint32(asvm->names, TK_OBJECT_PROP_SIZE, 0);
    uint32_t lots_len = tk_object_get_prop_uint32(asvm->lots, TK_OBJECT_PROP_SIZE, 0);

    b = (asvm->selected_name_index >= 0) &&
              (asvm->selected_lot_index >= 0) &&
              (asvm->selected_name_index < names_len) &&
              (asvm->selected_lot_index < lots_len);
  }

  return b;
}

static const char* _get_item_name(assay_selector_vm_t* asvm, int i) {
    char buf[16];

    tk_snprintf(buf, sizeof(buf), "[%d]", i);
    const char* name = tk_object_get_prop_str(asvm->names, buf);

    return (name != NULL) ? name : "";
}

static int _get_item_lot(assay_selector_vm_t* asvm, int i) {
    char buf[16];

    tk_snprintf(buf, sizeof(buf), "[%d]", i);
    return tk_object_get_prop_int(asvm->names, buf, 0);
}

static ret_t _exec(tk_object_t* obj, const char* name, const char* args) {
  ret_t ret = RET_NOT_IMPL;
  assay_selector_vm_t* asvm = _cast(obj);
  return_value_if_fail(asvm != NULL, RET_BAD_PARAMS);

  if(tk_str_eq(name, "select_name")) {
      tk_object_t* a = object_default_create();
      tk_command_arguments_to_object(args, a);
      int selected_index = tk_object_get_prop_int32(a, "index", asvm->selected_name_index);
      tk_object_unref(a);

      if(asvm->selected_name_index == selected_index) {
          ret = RET_OK;
      } else {
          asvm->selected_name_index = selected_index;

          char where[64];
          const char* selected_name = _get_item_name(asvm, asvm->selected_name_index);

          tk_snprintf(where, sizeof(where), "name='%s'", selected_name);
          assay_repository_select_rows("lot", where, asvm->lots);
          asvm->selected_lot_index = -1;

          uint32_t lots_size = tk_object_get_prop_uint32(asvm->lots, TK_OBJECT_PROP_SIZE, 0);
          log_debug("lot list size: %u\n", lots_size);

          ret = RET_ITEMS_CHANGED;
      }
  } else if(tk_str_eq(name, "select_lot")) {
      tk_object_t* a = object_default_create();
      tk_command_arguments_to_object(args, a);
      int selected_index = tk_object_get_prop_int32(a, "index", asvm->selected_lot_index);
      tk_object_unref(a);

      if(asvm->selected_lot_index == selected_index) {
          ret = RET_OK;
      } else {
          asvm->selected_lot_index = selected_index;
          ret = RET_OBJECT_CHANGED;
      }
  } else if(tk_str_eq(name, "confirm")) {
      assay_id_t aid;

      tk_strncpy(aid.name, _get_item_name(asvm, asvm->selected_name_index), sizeof(aid.name));
      aid.lot = _get_item_lot(asvm, asvm->selected_lot_index);
      pubsub_publish(pubsub_topic_selected_assay, &aid, sizeof(aid));
      ret = navigator_back();
  }

  if (ret == RET_OBJECT_CHANGED) {
//    emitter_dispatch_simple_event(EMITTER(obj), EVT_PROPS_CHANGED);
//    emitter_dispatch_simple_event(EMITTER(obj), EVT_ITEMS_CHANGED);
      ret = RET_ITEMS_CHANGED;
  }

  return ret;
}

static ret_t _destroy(tk_object_t* obj) {
  assay_selector_vm_t* asvm = _cast(obj);
  return_value_if_fail(asvm != NULL, RET_BAD_PARAMS);

  tk_object_unref(asvm->names);
  tk_object_unref(asvm->lots);

  return RET_OK;
}

static const object_vtable_t _vtable = {
    .type = "assay_selector_vm_t",
    .desc = "assay selector VM",
    .size = sizeof(assay_selector_vm_t),
    .is_collection = false,
    .exec = _exec,
    .can_exec = _can_exec,
    .remove_prop = _remove_prop,
    .get_prop = _get_prop,
    .set_prop = _set_prop,
    .on_destroy = _destroy,
};

static assay_selector_vm_t* _cast(tk_object_t* obj) {
  return_value_if_fail(obj != NULL && obj->vt == &_vtable, NULL);

  return (assay_selector_vm_t*)obj;
}

static const char* _preprocess_prop(view_model_t* view_model, const char* prop) {
  char index[TK_NUM_MAX_LEN + 1];
  view_model_array_t* vm_array = VIEW_MODEL_ARRAY(view_model);
  return_value_if_fail(view_model != NULL && prop != NULL, NULL);

  if (tk_str_eq(prop, "item")) {
      tk_snprintf(index, TK_NUM_MAX_LEN, "[%d]", vm_array->cursor);
      str_set(&(vm_array->temp_prop), prop);
      str_replace(&(vm_array->temp_prop), "item", index);

      return vm_array->temp_prop.str;
  } else if (tk_str_start_with(prop, "item")) {
    tk_snprintf(index, TK_NUM_MAX_LEN, "[%d].", vm_array->cursor);
    str_set(&(vm_array->temp_prop), prop);
    str_replace(&(vm_array->temp_prop), "item_", index);
    str_replace(&(vm_array->temp_prop), "item.", index);

    return vm_array->temp_prop.str;
  } else if(tk_str_start_with(prop, "selected.")) {
    tk_snprintf(index, TK_NUM_MAX_LEN, "[%d].", vm_array->selected_index);
    str_set(&(vm_array->temp_prop), prop);
    str_replace(&(vm_array->temp_prop), "selected.", index);

    return vm_array->temp_prop.str;
  } else {
    return prop;
  }
}

view_model_t* assay_selector_vm_create(navigator_request_t* req) {
  tk_object_t* obj = tk_object_create(&_vtable);
  view_model_t* vm = view_model_array_init(VIEW_MODEL(obj));
  assay_selector_vm_t* asvm =  _cast(obj);
  return_value_if_fail(asvm != NULL, NULL);

  vm->preprocess_prop = _preprocess_prop;

  asvm->names = object_array_create();
  asvm->lots = object_array_create();
  asvm->selected_name_index = -1;
  asvm->selected_lot_index = -1;

  assay_repository_select_rows("name", NULL, asvm->names);
  assay_repository_select_rows("lot", "name='CK-MB'", asvm->lots);

  return vm;
}
@xianjimli
Copy link
Member

xianjimli commented Oct 6, 2024

不行。你可以用两个ViewModel对应到同一个Model上。
view1 -> view_model1 -> model
view2 -> view_model2 -> model

@xuchaoze
Copy link
Collaborator

xuchaoze commented Dec 12, 2024

返回值为 RET_ITEMS_CHANGED 仅表示 ViewModel 自身的项目数量发生变化。如果v-for绑定对象为ViewModel内的一个对象,有两种方式通知变化:
1、对象上的项目数量发生变化时,可以手动调用 view_model_notify_items_changed 来通知界面变化;
2、对象创建时 emitter_on(EMITTER(asvm->names), EVT_ITEMS_CHANGED, emitter_forward, vm); ,则该对象分发 EVT_ITEMS_CHANGED 事件时会通知界面变化。

@ufbycd
Copy link
Author

ufbycd commented Dec 12, 2024

返回值为 RET_ITEMS_CHANGED 仅表示 ViewModel 自身的项目数量发生变化。如果v-for绑定对象为ViewModel内的一个对象,有两种方式通知变化: 1、对象上的项目数量发生变化时,可以手动调用 view_model_notify_items_changed 来通知界面变化; 2、对象创建时 emitter_on(EMITTER(asvm->names), EVT_ITEMS_CHANGED, emitter_forward, vm); ,则该对象分发 EVT_ITEMS_CHANGED 事件时会通知界面变化。

试过了,不行的。要按(#64)那里打补丁修改awtk-mvvm才行。

@ufbycd ufbycd reopened this Dec 12, 2024
@xuchaoze
Copy link
Collaborator

可以参考demo35的实现。如果方便的话,可以发一个可以重现的简单C实例看看。

@xuchaoze
Copy link
Collaborator

完善了demo13,添加了显示两列表的示例,可以参考

@ufbycd
Copy link
Author

ufbycd commented Dec 13, 2024

完善了demo13,添加了显示两列表的示例,可以参考

好的,我再看看,之前可能有东西没搞对。

@ufbycd
Copy link
Author

ufbycd commented Dec 28, 2024

还是有问题:渲染v-for="{items}"时要求getProp items.#size回复的数据类型必须为tk_object_t*类型。
然而Zig这边响应getProp是由Zig struct实现的,而非tk_object_t。于是就要非常拙劣地添加下面这个Zig struct来响应items.#size:

const Size = struct {
    @"#size": u32 = 0,

    pub const Object = object.Object(Size, .{});

    pub fn init(_: *Size) void {}
    pub fn deinit(_: *Size) void {}

    pub fn set(obj: *awtk.Object, size: usize) !void {
        const size_obj = Object.cast(obj) orelse return object.Error.Mismatch;
        const s: u32 = @intCast(size);
        if (size_obj.model.@"#size" != s) {
            size_obj.model.@"#size" = s;
            awtk.notify(obj, c.EVT_ITEMS_CHANGED);
        }
    }
};

详见这里

如果awtk-mvvm打上了(#64)所术补丁,则不需要如此拙劣。

@ufbycd
Copy link
Author

ufbycd commented Jan 1, 2025

单个view-model显示两个列表后,切换语言时第一个列表内的combo_box的options没有自动切换翻译:
需要在切换语言后,手动修改第一个列表内所有combo_box的选中项之后,才会随语言切换时自动切换其翻译。
屏幕截图_20250101_204536
两个列表的UI如下:

  <list_view x="c" y="290" w="90%" h="240" item_height="40">
    <row x="0" y="0" w="100%" h="40" children_layout="default(rows=1,cols=4,s=0)">
      <label style="table_title" tr_text="姓名" />
      <label style="table_title" tr_text="年龄" />
      <label style="table_title" tr_text="性别" />
      <label style="table_title" tr_text="操作" />
    </row>
    <scroll_view x="0" y="40" w="100%" h="-40">
      <list_item v-for="{users1}" w="100%" children_layout="default(rows=1,cols=4,s=0)">
        <edit input_type="ascii" v-data:text="{item.name}" />
        <edit input_type="uint" min="0" max="150" auto_fix="true" v-data:value="{item.age}" />
        <combo_box readonly="true" options="Male;Female;" v-data:value="{item.gender}" />
        <view children_layout="default(rows=1,cols=2,s=10,xm=10)">
          <button tr_text="删除" v-on:click="{remove, Args=fscript?index=index&amp;gender='male'}" />
          <button tr_text="编辑" v-on:click="{edit, Args=fscript?index=index&amp;gender='male'}" />
        </view>
      </list_item>
    </scroll_view>
    <scroll_bar_m x="right" y="40" w="6" h="-40" value="0" />
  </list_view>

  <list_view x="c" y="570" w="90%" h="200" item_height="40">
    <scroll_view x="0" y="0" w="100%" h="100%">
      <list_item v-for="{users2}" w="100%" children_layout="default(rows=1,cols=4,s=0)">
        <edit input_type="ascii" v-data:text="{item.name}" />
        <edit input_type="uint" min="0" max="150" auto_fix="true" v-data:value="{item.age}" />
        <combo_box readonly="true" options="Male;Female;" v-data:value="{item.gender}" />
        <view children_layout="default(rows=1,cols=2,s=10,xm=10)">
          <button tr_text="删除" v-on:click="{remove, Args=fscript?index=index&amp;gender='female'}" />
          <button tr_text="编辑" v-on:click="{edit, Args=fscript?index=index&amp;gender='female'}" />
        </view>
      </list_item>
    </scroll_view>
    <scroll_bar_m x="right" y="0" w="6" h="100%" value="0" />
  </list_view>

@xuchaoze
Copy link
Collaborator

切换语言时第一个列表内的combo_box的options没有自动切换翻译的问题,更新awtk 到55c8f8d0e2b5d7a5d5120fbd6eb69151c60188f0 以上 试试

@ufbycd
Copy link
Author

ufbycd commented Jan 13, 2025

切换语言时第一个列表内的combo_box的options没有自动切换翻译的问题,更新awtk 到55c8f8d0e2b5d7a5d5120fbd6eb69151c60188f0 以上 试试

这个问题似乎是改了,但引入了个更大问题:View-Model的初始数据被View在显示时修改了,而且被修改了两次。View显示时的调试信息如下:

debug: application.init
debug: language changed: Chinese
debug: registered view-model: UserVM
debug: registered view-model: UsersVM
debug: UsersVM.init
view_model_on_will_mount : Obj.UsersVM
try font load default_zh_CN
try font load default_zh
load asset default
debug: viewmodels.users_vm.UsersVM setProp: languageId
try font load default_en_US
try font load default_en
load asset default
debug: viewmodels.users_vm.UsersVM setProp: languageId
debug: language changed: English
debug: viewmodels.users_vm.UsersVM setProp: cfg.age
debug: language changed: English
view_model_on_mount : Obj.UsersVM

从而导致程序在初始化时设置界面显示为中文这个操作无效,程序打开后显示的是英文;旧版本没有问题。
初步分析这两次修改分别依次由这两者触发:view_model_on_will_mount和locale_info_change函数发出的EVT_LOCALE_CHANGED事件(我的应用代码并没有绑定EVT_LOCALE_CHANGED事件)。

另外新版本的AWTK软件运行时每次都会弹出下面这种警告框两次(旧版是没有的):
屏幕截图_20250113_210413
你们的没有出现吗?

@xuchaoze
Copy link
Collaborator

界面初始化时设置为中文具体是怎样

@ufbycd
Copy link
Author

ufbycd commented Jan 13, 2025

界面初始化时设置为中文具体是怎样

跟combo_box绑定的languageId的初始值是1的,界面初始化后其值被View改成0了。

<combo_box options="English;Chinese;" v-data:value="{languageId}" />

@xuchaoze
Copy link
Collaborator

#62 (comment)
1、程序打开后显示的是英文是由于你代码中languageId的初值是英文(awtk.zig中 pub var language:Language = .English),不是中文;
2、断言这边用 纯C Demo没出现,你是什么平台的

@ufbycd
Copy link
Author

ufbycd commented Jan 14, 2025

src/application.zig:

pub fn init() !void {
    std.log.debug("{s}.{s}", .{ @typeName(@This()), @src().fn_name });

    try awtk.setLanguage(.Chinese);
    try models.init(.{});
    try viewmodels.init();

    try mvvm.navigatorTo("win_main");
}
操作系统: Manjaro Linux 
KDE Plasma 版本: 6.2.4
KDE 程序框架版本: 6.8.0
Qt 版本: 6.8.1
内核版本: 6.6.65-1-MANJARO (64 位)
图形平台: X11
处理器: 8 × Intel® Core™ i7-9700 CPU @ 3.00GHz
内存: 30.3 GiB 内存
图形处理器: Mesa Intel® UHD Graphics 630
制造商: LENOVO

@xuchaoze
Copy link
Collaborator

我这边用纯C没重现出来,没你的那个环境,可能要你帮忙跟踪调试看看

@ufbycd
Copy link
Author

ufbycd commented Jan 15, 2025

Ubuntu上也是一样呀,跑AWTK的demo都出问题。Linux环境不应该测试下么?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants