You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xamarin中MVVM命令执行后页面标签未刷新问题

电商APP购物车删除商品后总价与积分标签不更新问题解决

我开发了一款电商APP的购物车页面,在XAML的数据模板外设置了显示订单总价和累计积分的Label控件。ViewModel中实现了购物车商品删除命令,但删除商品后,订单总价与积分标签无法自动更新,相关代码如下:

XAML代码

<Button ImageSource="bin.png"
        x:Name="btnDelete"
        Command="{Binding Source={x:Reference cartCollectionView}, Path=BindingContext.CartsCommand}" 
        CommandParameter="{Binding .}"
        Clicked="btnDelete_Clicked"
        BackgroundColor="Transparent"
        HorizontalOptions="EndAndExpand"/>

                    </StackLayout>
                </StackLayout>
            </Frame>
        </StackLayout>
    </DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<StackLayout Orientation="Horizontal">
    <Label Text="Order #:"
           FontSize="20"
           TextColor="Black"/>
    <Label x:Name="txtOrderNumber"
          FontSize="20" 
           TextColor="Black"/>
</StackLayout>
<StackLayout Orientation="Horizontal">
    <Label Text="Total Woof Bits earned:" FontSize="Title" Margin="0,0,0,0" TextColor="Black"/>
    <Label x:Name="txtPoints" Text="{Binding strPoints}" FontSize="Title" Margin="0,0,0,0" TextColor="Black"/>
</StackLayout>

<StackLayout Orientation="Horizontal">
    <Label Text="Total Order:" FontSize="Title" TextColor="Black"/>
    <Label  x:Name="txtTotalOrder" Text="{Binding strTotalOrder}" FontSize="Title" TextColor="Black"/>
</StackLayout>

View代码

public CartPage(CartViewModel viewModels)
{
    InitializeComponent();
    BindingContext = viewModels;
    BindingContext = new CartViewModel();
    ((CartViewModel)BindingContext).LoadCartsCommand.Execute(null);
    ((CartViewModel)BindingContext).LoadTotalsCommand.Execute(null);

    txtOrderNumber.Text = SigninPage.strOrderNumber;
    txtPoints.Text = CartViewModel.strPoints;
    txtTotalOrder.Text = CartViewModel.strTotalOrder;
}

protected override void OnAppearing()
{
    base.OnAppearing();

    ((CartViewModel)BindingContext).LoadCartsCommand.Execute(null);
    ((CartViewModel)BindingContext).LoadTotalsCommand.Execute(null);
    txtPoints.Text = CartViewModel.strPoints;
    txtTotalOrder.Text = CartViewModel.strTotalOrder;
}

private void Button_Clicked(object sender, EventArgs e)
{
    ((CartViewModel)BindingContext).PayCommand.Execute(null);
}

private void btnDelete_Clicked(object sender, EventArgs e)
{
    ((CartViewModel)BindingContext).LoadTotalsCommand.Execute(null);

    txtPoints.Text = CartViewModel.strPoints;
    txtTotalOrder.Text = CartViewModel.strTotalOrder;
    DisplayAlert("Successful", "Item removed from Cart (▼´•̥ᴥ•̥`) ", "OK");
}

ViewModel代码

private ICommand _loadTotalsCommand;

public ICommand LoadTotalsCommand
{
    get
    {
        if (_loadTotalsCommand == null)
        {
            _loadTotalsCommand = new Command(async () => await LoadTotals());
            OnPropertyChanged();
        }

        return _loadTotalsCommand;
    }
}

public async Task LoadTotals()
{
    string sqlstring = "MySQL string";
    MySqlConnection mysqlcon = new MySqlConnection(sqlstring);
    MySqlCommand mysqlcom;
    MySqlDataReader mdr;

    mysqlcon.Open();

    string selectquery = "SELECT ordernumber, quantity, price, sum(price * quantity) AS Total, sum(pointvalue) AS Total1 FROM cart WHERE ordernumber = '" + SigninPage.strOrderNumber + "' GROUP BY ordernumber;";
    mysqlcom = new MySqlCommand(selectquery, mysqlcon);

    mdr = mysqlcom.ExecuteReader();

    while (mdr.Read())
        if (mdr.FieldCount >= 1)
        {
            TotalOrder = mdr.GetDecimal(3);
            Points = mdr.GetDecimal(4);
        }

    mysqlcon.Close();
    strTotalOrder = "R" + Convert.ToString(TotalOrder);
    strPoints = "🐾" + Convert.ToString(Points);
}

private ICommand _cartCommand;

public ICommand CartsCommand
{
    get
    {
        if (_cartCommand == null)
        {
            _cartCommand = new Command<Cart>(async (cart) =>
            {
                await OnCartSelected(cart);
            });
        }

        return _cartCommand;
    }
}

public async Task OnCartSelected(Cart cart)
{
    string constring5 = "MySQL string";
    MySqlConnection con5 = new MySqlConnection(constring5);
    con5.Open();
    MySqlTransaction transaction5 = con5.BeginTransaction();

    using (MySqlCommand cmd5 = new MySqlCommand("DELETE FROM cart WHERE ordernumber = '" + strOrderNumber4 + "'", con5))
    {
        cmd5.Transaction = transaction5;
        cmd5.ExecuteNonQuery();
        transaction5.Commit();
        con5.Close();
    }

    LoadTotalsCommand.Execute(null);
    LoadCartsCommand.Execute(null);
}

问题根源与解决方案

核心问题

  1. ViewModel未实现INotifyPropertyChanged接口:strTotalOrder和strPoints属性更新时,没有通知UI刷新,这是数据绑定不生效的关键原因。
  2. View手动赋值破坏绑定:在构造函数和OnAppearing中直接给Label的Text赋值,覆盖了XAML中的数据绑定逻辑。
  3. 删除SQL逻辑错误:OnCartSelected中使用未定义的strOrderNumber4变量,且未使用参数化查询,存在SQL注入风险。
  4. 异步操作不规范:数据库操作未使用异步方法,可能导致UI阻塞。

修正步骤

1. 让ViewModel实现INotifyPropertyChanged

public class CartViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    // 原有成员变量和方法...
}

2. 将strTotalOrder和strPoints改为可通知属性

private string _strTotalOrder;
public string strTotalOrder
{
    get => _strTotalOrder;
    set
    {
        _strTotalOrder = value;
        OnPropertyChanged();
    }
}

private string _strPoints;
public string strPoints
{
    get => _strPoints;
    set
    {
        _strPoints = value;
        OnPropertyChanged();
    }
}

3. 移除View中的手动赋值代码

修改CartPage构造函数和OnAppearing方法:

public CartPage(CartViewModel viewModels)
{
    InitializeComponent();
    // 复用注入的ViewModel实例,避免重复创建
    BindingContext = viewModels ?? new CartViewModel();
    ((CartViewModel)BindingContext).LoadCartsCommand.Execute(null);
    ((CartViewModel)BindingContext).LoadTotalsCommand.Execute(null);

    txtOrderNumber.Text = SigninPage.strOrderNumber;
    // 移除手动赋值,保留数据绑定
    // txtPoints.Text = CartViewModel.strPoints;
    // txtTotalOrder.Text = CartViewModel.strTotalOrder;
}

protected override void OnAppearing()
{
    base.OnAppearing();

    ((CartViewModel)BindingContext).LoadCartsCommand.Execute(null);
    ((CartViewModel)BindingContext).LoadTotalsCommand.Execute(null);
    // 移除手动赋值
    // txtPoints.Text = CartViewModel.strPoints;
    // txtTotalOrder.Text = CartViewModel.strTotalOrder;
}

private void btnDelete_Clicked(object sender, EventArgs e)
{
    // 删除后命令已自动刷新数据,无需手动调用
    // ((CartViewModel)BindingContext).LoadTotalsCommand.Execute(null);
    // txtPoints.Text = CartViewModel.strPoints;
    // txtTotalOrder.Text = CartViewModel.strTotalOrder;
    DisplayAlert("Successful", "Item removed from Cart (▼´•̥ᴥ•̥`) ", "OK");
}

4. 修正删除逻辑并使用参数化查询

public async Task OnCartSelected(Cart cart)
{
    string constring5 = "MySQL string";
    using (MySqlConnection con5 = new MySqlConnection(constring5))
    {
        await con5.OpenAsync();
        using (MySqlTransaction transaction5 = con5.BeginTransaction())
        {
            // 使用参数化查询,删除指定订单下的指定商品
            using (MySqlCommand cmd5 = new MySqlCommand("DELETE FROM cart WHERE ordernumber = @orderNumber AND id = @cartId", con5))
            {
                cmd5.Transaction = transaction5;
                cmd5.Parameters.AddWithValue("@orderNumber", SigninPage.strOrderNumber);
                cmd5.Parameters.AddWithValue("@cartId", cart.Id); // 假设Cart类包含Id属性
                await cmd5.ExecuteNonQueryAsync();
                await transaction5.CommitAsync();
            }
        }
    }

    // 异步执行刷新命令
    await ((Command)LoadTotalsCommand).ExecuteAsync(null);
    await ((Command)LoadCartsCommand).ExecuteAsync(null);
}

5. 优化LoadTotals方法的异步逻辑

public async Task LoadTotals()
{
    string sqlstring = "MySQL string";
    using (MySqlConnection mysqlcon = new MySqlConnection(sqlstring))
    {
        await mysqlcon.OpenAsync();
        // 简化查询,只获取需要的总价和积分
        string selectquery = "SELECT sum(price * quantity) AS Total, sum(pointvalue) AS Total1 FROM cart WHERE ordernumber = @orderNumber GROUP BY ordernumber;";
        using (MySqlCommand mysqlcom = new MySqlCommand(selectquery, mysqlcon))
        {
            mysqlcom.Parameters.AddWithValue("@orderNumber", SigninPage.strOrderNumber);
            using (MySqlDataReader mdr = await mysqlcom.ExecuteReaderAsync())
            {
                if (await mdr.ReadAsync())
                {
                    TotalOrder = mdr.GetDecimal(0);
                    Points = mdr.GetDecimal(1);
                }
                else
                {
                    // 购物车为空时重置数值
                    TotalOrder = 0;
                    Points = 0;
                }
            }
        }
    }
    strTotalOrder = "R" + TotalOrder.ToString();
    strPoints = "🐾" + Points.ToString();
}

内容的提问来源于stack exchange,提问作者SUI

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 16:54:56