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 · 7 comments
Open

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

ufbycd opened this issue Sep 29, 2024 · 7 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)所术补丁,则不需要如此拙劣。

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