如何从另一个 ViewModel 中访问 ObservableCollection?

huangapple go评论70阅读模式
英文:

How to access ObservableCollection from another ViewModel?

问题

I have a BaseViewModel where I define my ObservableCollection and then I populate it. In another ViewModel I have a Grid and I bind it to my collection, everything works.

public partial class BaseViewModel : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection<Patient> patientInfo;

    public BaseViewModel()
    {
        PopulateObservableCollection();
    }

    public void PopulateObservableCollection()
    {
        PatientInfo = new ObservableCollection<Patient>();
        try
        {
            string query = "select * from " + App.userDetails.DbTable;
            SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(query, conn);
            using (sqlDataAdapter)
            {
                System.Data.DataTable dt = new System.Data.DataTable();
                sqlDataAdapter.Fill(dt);
                foreach (System.Data.DataRow row in dt.Rows)
                    PatientInfo.Add(new Patient(Convert.ToInt32(row[0]), row[1].ToString(), row[2].ToString(), row[3].ToString(), row[4].ToString(), row[5].ToString(), row[6].ToString(), row[7].ToString(), row[8].ToString()));
            }
        }
        catch (Exception)
        { }
    }
}

Now I want to change my ObservableCollection from the other ViewModels and be able to access the memory from the BaseViewModel.

Next a ViewModel where I try to change the ObservableCollection, but I'm not accessing the objected created in the BaseViewModel:

[QueryProperty(nameof(Patient), nameof(Patient))]
public partial class EditingViewModel : BaseViewModel
{
    [ObservableProperty] private Patient patient;
    
    [RelayCommand]
    private async Task Delete()
    {
        try
        {
            string query = "DELETE from " + App.userDetails.DbTable + " WHERE id = @patientId";
            sqlConnection = new SqlConnection(conn);
            SqlCommand sqlCommand = new SqlCommand(query, sqlConnection);
            SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(sqlCommand);

            using (sqlDataAdapter)
            {
                sqlConnection.Open();
                sqlCommand.Parameters.AddWithValue("@patientId", Patient.Id);
                sqlCommand.ExecuteScalar();
            }

            //Removing object from observable collection
            PatientInfo.Remove(PatientInfo.SingleOrDefault(x => x.Id == Patient.Id));
        }
        catch (Exception ex)
        {
            await Shell.Current.DisplayAlert("Aplicação", ex.ToString(), "Sair");
        }
        finally
        {
            sqlConnection.Close();
        }
    }
}

My "DashboardView" where the grid is defined:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             x:Class="PatientAppMultiPlatf.Views.DashboardPage"
             xmlns:viewModels="clr-namespace:PatientAppMultiPlatf.ViewModels"
             xmlns:syncfusion="clr-namespace:Syncfusion.Maui.DataGrid;assembly=Syncfusion.Maui.DataGrid"
             Title="Lista de Pacientes">

    <VerticalStackLayout Padding="10">
        <HorizontalStackLayout Padding="20" >
            <Button 
                   Text="Exportar" Command="{Binding PDFCommand}" CornerRadius="20" WidthRequest="100" HorizontalOptions="End">
            </Button>
            <Button
                Text="Filtrar" Command="{Binding FilterCommand}" WidthRequest="100" CornerRadius="20" HorizontalOptions="Center">
            </Button>
            <Frame HeightRequest="45" Padding="5" HasShadow="True" BorderColor="White">
                <Entry Text="{Binding Date}" VerticalOptions="Center" Placeholder="Data"/>
            </Frame>
            <Frame HeightRequest="45" Padding="5" HasShadow="True" BorderColor="White">
                <Entry Text="{Binding Name}" VerticalOptions="Center" Placeholder="Nome"/>
            </Frame>
            <Frame HeightRequest="45" Padding="5" HasShadow="True" BorderColor="White">
                <Entry Text="{Binding Age}" VerticalOptions="Center" Placeholder="Idade"/>
            </Frame>
            <Frame HeightRequest="45" Padding="5" HasShadow="True" BorderColor="White">
                <Entry Text="{Binding Gender}" VerticalOptions="Center" Placeholder="Genero"/>
            </Frame>
            <Frame HeightRequest="45" Padding="5" HasShadow="True" BorderColor="White">
                <Entry Text="{Binding Process}" VerticalOptions="Center" Placeholder="Processo"/>
            </Frame>
            <Frame HeightRequest="45" Padding="5" HasShadow="True" BorderColor="White">
                <Entry Text="{Binding Diagnostic}" VerticalOptions="Center" Placeholder="Diagnostico"/>
            </Frame>
            <Frame HeightRequest="45" Padding="5" HasShadow="True" BorderColor="White">
                <Entry Text="{Binding Discharge}" VerticalOptions="Center" Placeholder="Alta"/>
            </Frame>

        </HorizontalStackLayout>
        <syncfusion:SfDataGrid x:Name="dataGrid" DefaultColumnWidth="90"
            ItemsSource="{Binding PatientInfo}" SortingMode="Single" SelectedRow="{Binding SelectedPatient}" SelectionMode="Single"
                               HeightRequest="600"  VerticalOptions="CenterAndExpand" HorizontalOptions="Center" AutoGenerateColumnsMode="None">
            <syncfusion:SfDataGrid.Behaviors>
                <toolkit:EventToCommandBehavior
                    EventName="CellDoubleTapped"
                    Command="{Binding CellDoubleTappedCommand}">
                </toolkit:EventToCommandBehavior>
            </syncfusion:SfDataGrid.Behaviors>
            <syncfusion:SfDataGrid.Columns VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
                <syncfusion:DataGridTextColumn  HeaderText="Data" MappingName="Date"/>
                <syncfusion:DataGridTextColumn  HeaderText="Nome" MappingName="Name" ColumnWidthMode="Fill"/>
                <syncfusion:DataGridTextColumn  HeaderText="Idade" MappingName="Age"/>
                <syncfusion:DataGridTextColumn  HeaderText="Genero" MappingName="Gender"/>
                <syncfusion:DataGridTextColumn  HeaderText="Processo" MappingName="Process"/>
                <syncfusion:DataGridTextColumn  HeaderText="Diagnostico" MappingName="Diagnostic" ColumnWidthMode="Fill"/>
                <syncfusion:DataGridTextColumn  HeaderText="Alta" MappingName="Discharge" ColumnWidthMode="LastColumnFill"/>
            </syncfusion:SfDataGrid.Columns>
                        <syncfusion:SfDataGrid.DefaultStyle>
                <syncfusion:DataGridStyle HeaderRowBackground="#b2cbc8" HeaderRowTextColor="Black" />
            </syncfusion:SfDataGrid.DefaultStyle>
        </syncfusion:SfDataGrid>
    </VerticalStackLayout>
</ContentPage>

Each view has a xaml.cs where I bind the page to the view model:

namespace PatientAppMultiPlatf.Views;

public partial class DashboardPage : ContentPage
{
    public DashboardPage(DashboardViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

I'm using MVVM architecture. What is the best method to access the ObservableCollection that is being

英文:

I have a BaseViewModel where I define my ObservableCollection and then I populate it. In another ViewModel I have a Grid and I bind it to my collection, everything works.

public partial class BaseViewModel : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection&lt;Patient&gt; patientInfo;

    public BaseViewModel()
    {
        PopulateObservableCollection();
    }

    public void PopulateObservableCollection()
    {
        PatientInfo = new ObservableCollection&lt;Patient&gt;();
        try
        {
            string query = &quot;select * from &quot; + App.userDetails.DbTable;
            SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(query, conn);
            using (sqlDataAdapter)
            {
                System.Data.DataTable dt = new System.Data.DataTable();
                sqlDataAdapter.Fill(dt);
                foreach (System.Data.DataRow row in dt.Rows)
                    PatientInfo.Add(new Patient(Convert.ToInt32(row[0]), row[1].ToString(), row[2].ToString(), row[3].ToString(), row[4].ToString(), row[5].ToString(), row[6].ToString(), row[7].ToString(), row[8].ToString()));
            }
        }
        catch (Exception)
        { }
    }
}

Now I want to change my ObservableCollection from the other ViewModels and be able to access the memory from the BaseViewModel.

Next a ViewModel where I try to change the ObservableCollection, but I'm not accessing the objected created in the BaseViewModel:

[QueryProperty(nameof(Patient), nameof(Patient))]
public partial class EditingViewModel : BaseViewModel
{
    [ObservableProperty] private Patient patient;
    
    [RelayCommand]
    private async Task Delete()
    {
        try
        {
            string query = &quot;DELETE from &quot; + App.userDetails.DbTable + &quot; WHERE id = @patientId&quot;;
            sqlConnection = new SqlConnection(conn);
            SqlCommand sqlCommand = new SqlCommand(query, sqlConnection);
            SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(sqlCommand);

            using (sqlDataAdapter)
            {
                sqlConnection.Open();
                sqlCommand.Parameters.AddWithValue(&quot;@patientId&quot;, Patient.Id);
                sqlCommand.ExecuteScalar();
            }

            //Removing object from observable collection
            PatientInfo.Remove(PatientInfo.SingleOrDefault(x =&gt; x.Id == Patient.Id));
        }
        catch (Exception ex)
        {
            await Shell.Current.DisplayAlert(&quot;Aplica&#231;ao&quot;, ex.ToString(), &quot;Sair&quot;);
        }
        finally
        {
            sqlConnection.Close();
        }
    }
}

My "DashboardView" where the grid is defined:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
&lt;ContentPage xmlns=&quot;http://schemas.microsoft.com/dotnet/2021/maui&quot;
             xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;
             xmlns:toolkit=&quot;http://schemas.microsoft.com/dotnet/2022/maui/toolkit&quot;
             x:Class=&quot;PatientAppMultiPlatf.Views.DashboardPage&quot;
             xmlns:viewModels=&quot;clr-namespace:PatientAppMultiPlatf.ViewModels&quot;
             xmlns:syncfusion=&quot;clr-namespace:Syncfusion.Maui.DataGrid;assembly=Syncfusion.Maui.DataGrid&quot;
             Title=&quot;Lista de Pacientes&quot;&gt;

    &lt;VerticalStackLayout Padding=&quot;10&quot;&gt;
        &lt;HorizontalStackLayout Padding=&quot;20&quot; &gt;
            &lt;Button 
                   Text=&quot;Exportar&quot; Command=&quot;{Binding PDFCommand}&quot; CornerRadius=&quot;20&quot; WidthRequest=&quot;100&quot; HorizontalOptions=&quot;End&quot;&gt;
            &lt;/Button&gt;
            &lt;Button
                Text=&quot;Filtrar&quot; Command=&quot;{Binding FilterCommand}&quot; WidthRequest=&quot;100&quot; CornerRadius=&quot;20&quot; HorizontalOptions=&quot;Center&quot;&gt;
            &lt;/Button&gt;
            &lt;Frame HeightRequest=&quot;45&quot; Padding=&quot;5&quot; HasShadow=&quot;True&quot; BorderColor=&quot;White&quot;&gt;
                &lt;Entry Text=&quot;{Binding Date}&quot; VerticalOptions=&quot;Center&quot; Placeholder=&quot;Data&quot;/&gt;
            &lt;/Frame&gt;
            &lt;Frame HeightRequest=&quot;45&quot; Padding=&quot;5&quot; HasShadow=&quot;True&quot; BorderColor=&quot;White&quot;&gt;
                &lt;Entry Text=&quot;{Binding Name}&quot; VerticalOptions=&quot;Center&quot; Placeholder=&quot;Nome&quot;/&gt;
            &lt;/Frame&gt;
            &lt;Frame HeightRequest=&quot;45&quot; Padding=&quot;5&quot; HasShadow=&quot;True&quot; BorderColor=&quot;White&quot;&gt;
                &lt;Entry Text=&quot;{Binding Age}&quot; VerticalOptions=&quot;Center&quot; Placeholder=&quot;Idade&quot;/&gt;
            &lt;/Frame&gt;
            &lt;Frame HeightRequest=&quot;45&quot; Padding=&quot;5&quot; HasShadow=&quot;True&quot; BorderColor=&quot;White&quot;&gt;
                &lt;Entry Text=&quot;{Binding Gender}&quot; VerticalOptions=&quot;Center&quot; Placeholder=&quot;Genero&quot;/&gt;
            &lt;/Frame&gt;
            &lt;Frame HeightRequest=&quot;45&quot; Padding=&quot;5&quot; HasShadow=&quot;True&quot; BorderColor=&quot;White&quot;&gt;
                &lt;Entry Text=&quot;{Binding Process}&quot; VerticalOptions=&quot;Center&quot; Placeholder=&quot;Processo&quot;/&gt;
            &lt;/Frame&gt;
            &lt;Frame HeightRequest=&quot;45&quot; Padding=&quot;5&quot; HasShadow=&quot;True&quot; BorderColor=&quot;White&quot;&gt;
                &lt;Entry Text=&quot;{Binding Diagnostic}&quot; VerticalOptions=&quot;Center&quot; Placeholder=&quot;Diagnostico&quot;/&gt;
            &lt;/Frame&gt;
            &lt;Frame HeightRequest=&quot;45&quot; Padding=&quot;5&quot; HasShadow=&quot;True&quot; BorderColor=&quot;White&quot;&gt;
                &lt;Entry Text=&quot;{Binding Discharge}&quot; VerticalOptions=&quot;Center&quot; Placeholder=&quot;Alta&quot;/&gt;
            &lt;/Frame&gt;

        &lt;/HorizontalStackLayout&gt;
        &lt;syncfusion:SfDataGrid x:Name=&quot;dataGrid&quot; DefaultColumnWidth=&quot;90&quot;
            ItemsSource=&quot;{Binding PatientInfo}&quot; SortingMode=&quot;Single&quot; SelectedRow=&quot;{Binding SelectedPatient}&quot; SelectionMode=&quot;Single&quot;
                               HeightRequest=&quot;600&quot;  VerticalOptions=&quot;CenterAndExpand&quot; HorizontalOptions=&quot;Center&quot; AutoGenerateColumnsMode=&quot;None&quot;&gt;
            &lt;syncfusion:SfDataGrid.Behaviors&gt;
                &lt;toolkit:EventToCommandBehavior
                    EventName=&quot;CellDoubleTapped&quot;
                    Command=&quot;{Binding CellDoubleTappedCommand}&quot;&gt;
                &lt;/toolkit:EventToCommandBehavior&gt;
            &lt;/syncfusion:SfDataGrid.Behaviors&gt;
            &lt;syncfusion:SfDataGrid.Columns VerticalOptions=&quot;CenterAndExpand&quot; HorizontalOptions=&quot;CenterAndExpand&quot;&gt;
                &lt;syncfusion:DataGridTextColumn  HeaderText=&quot;Data&quot; MappingName=&quot;Date&quot;/&gt;
                &lt;syncfusion:DataGridTextColumn  HeaderText=&quot;Nome&quot; MappingName=&quot;Name&quot; ColumnWidthMode=&quot;Fill&quot;/&gt;
                &lt;syncfusion:DataGridTextColumn  HeaderText=&quot;Idade&quot; MappingName=&quot;Age&quot;/&gt;
                &lt;syncfusion:DataGridTextColumn  HeaderText=&quot;Genero&quot; MappingName=&quot;Gender&quot;/&gt;
                &lt;syncfusion:DataGridTextColumn  HeaderText=&quot;Processo&quot; MappingName=&quot;Process&quot;/&gt;
                &lt;syncfusion:DataGridTextColumn  HeaderText=&quot;Diagnostico&quot; MappingName=&quot;Diagnostic&quot; ColumnWidthMode=&quot;Fill&quot;/&gt;
                &lt;syncfusion:DataGridTextColumn  HeaderText=&quot;Alta&quot; MappingName=&quot;Discharge&quot; ColumnWidthMode=&quot;LastColumnFill&quot;/&gt;
            &lt;/syncfusion:SfDataGrid.Columns&gt;
                        &lt;syncfusion:SfDataGrid.DefaultStyle&gt;
                &lt;syncfusion:DataGridStyle HeaderRowBackground=&quot;#b2cbc8&quot; HeaderRowTextColor=&quot;Black&quot; /&gt;
            &lt;/syncfusion:SfDataGrid.DefaultStyle&gt;
        &lt;/syncfusion:SfDataGrid&gt;
    &lt;/VerticalStackLayout&gt;
&lt;/ContentPage&gt;

Each view has a xaml.cs where I bind the page to the view model:

namespace PatientAppMultiPlatf.Views;

public partial class DashboardPage : ContentPage
{
	public DashboardPage(DashboardViewModel viewModel)
	{
		InitializeComponent();
		BindingContext = viewModel;
	}
}

I'm using MVVM architecture. What is the best method to access the ObservableCollection that is being binded?

答案1

得分: 3

BaseViewModel中,将以下部分进行更改:

private ObservableCollection&lt;Patient&gt; patientInfo;

更改为:

protected ObservableCollection&lt;Patient&gt; patientInfo;
英文:

In the BaseViewModel, change

[ObservableProperty]
private ObservableCollection&lt;Patient&gt; patientInfo;

to

[ObservableProperty]
protected ObservableCollection&lt;Patient&gt; patientInfo;

答案2

得分: 1

ObservableCollections 不是 ObservableProperties。

对于集合本身的更改足以通知任何更改。

如果我担心将其设置为其他内容,通常会这样做:

public ObservableCollection MyProperty {get;} = new();

此外,我没有看到您的 x:DataType 在任何地方。为什么没有设置它,以及为什么您的 VisualElement 有名称?

编辑:我不确定我是否足够强调“PUBLIC”这个词,所以我想再次指出。

英文:

ObservableCollections are not ObservableProperties.

The change to the collection itself is more than enough to notify any changes.

If I am concerned about setting it to something else, I usually do this:

**public** ObservableCollection MyProperty {get;} = new();

Also, I do not see anywhere your x:DataType. Why are you not setting it, and why is your VisualElement named?

Edit: I am not sure I put enough stress on the word PUBLIC, so I want to point it out again here.

答案3

得分: 0

根据您的代码,我创建了一个演示,它可以从继承自父类(BaseViewModel)属性的PatientInfo中移除项目。您可以参考以下代码:

BaseViewModel.cs

public partial class BaseViewModel : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection<Patient> patientInfo;

    public BaseViewModel()
    {
        PopulateObservableCollection();
    }

    public void PopulateObservableCollection()
    {
        PatientInfo = new ObservableCollection<Patient>();
        try
        {
            PatientInfo.Add(new Patient { Id = 01, Name = "test1" });
            PatientInfo.Add(new Patient { Id = 02, Name = "test2" });
            PatientInfo.Add(new Patient { Id = 03, Name = "test3" });
        }
        catch (Exception)
        { }
    }
}

public class Patient
{
    public int Id { get; set; }
    public string Name { get; set; }
}

EditingViewModel.cs

public partial class EditingViewModel : BaseViewModel
{
    [RelayCommand]
    private async Task Delete()
    {
        try
        {
            // 从可观察集合中移除对象
            // 这里我可以从父类`BaseViewModel`的`PatientInfo`中移除项目
            PatientInfo.RemoveAt(0);
        }
        catch (Exception ex)
        {
            await Shell.Current.DisplayAlert("应用", ex.ToString(), "退出");
        }
        finally
        {
        }
    }
}

MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:MauiMvvmApp="clr-namespace:MauiMvvmApp226"
             x:Class="MauiMvvmApp226.MainPage">

    <ContentPage.BindingContext>
        <MauiMvvmApp:EditingViewModel></MauiMvvmApp:EditingViewModel>
    </ContentPage.BindingContext>

    <VerticalStackLayout
        Spacing="25"
        Padding="30,0"
        VerticalOptions="Start">

        <ListView ItemsSource="{Binding PatientInfo}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextCell Text="{Binding Id}"
                              Detail="{Binding Name}"
                              TextColor="#f35e20"
                              DetailColor="#503026" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <Button
            Command="{Binding DeleteCommand}"
            x:Name="CounterBtn"
            Text="点击我"
            HorizontalOptions="Center" />

    </VerticalStackLayout>
</ContentPage>
英文:

Based on your code,I created a demo and it could remove the item from PatientInfo which inherits from the properties of the parent class( BaseViewModel)

You can refer to the following code:

BaseViewModel.cs

public partial class BaseViewModel: ObservableObject 
{
[ObservableProperty]
private ObservableCollection&lt;Patient&gt; patientInfo;
public BaseViewModel()
{
PopulateObservableCollection();
}
public void PopulateObservableCollection()
{
PatientInfo = new ObservableCollection&lt;Patient&gt;();
try
{
PatientInfo.Add(new Patient { Id = 01, Name=&quot;test1&quot;});
PatientInfo.Add(new Patient { Id = 02, Name = &quot;test2&quot; });
PatientInfo.Add(new Patient { Id = 03, Name = &quot;test3&quot; });
}
catch (Exception)
{ }
}
}
public class Patient 
{
public int Id { get; set; }
public string Name { get; set; }
}

EditingViewModel.cs

   

  public partial class EditingViewModel : BaseViewModel 
{
//[ObservableProperty] private Patient patient;
[RelayCommand]
private async Task Delete()
{
try
{
//Removing object from observable collection
//here I could remove the item from PatientInfo from parent class `BaseViewModel `
PatientInfo.RemoveAt(0);
}
catch (Exception ex)
{
await Shell.Current.DisplayAlert(&quot;Aplica&#231;ao&quot;, ex.ToString(), &quot;Sair&quot;);
}
finally
{
}
}
}

MainPage.xaml

&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt; 
&lt;ContentPage xmlns=&quot;http://schemas.microsoft.com/dotnet/2021/maui&quot;
xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;
xmlns:MauiMvvmApp=&quot;clr-namespace:MauiMvvmApp226&quot;
x:Class=&quot;MauiMvvmApp226.MainPage&quot;&gt;
&lt;ContentPage.BindingContext&gt;
&lt;MauiMvvmApp:EditingViewModel&gt;&lt;/MauiMvvmApp:EditingViewModel&gt;
&lt;/ContentPage.BindingContext&gt;
&lt;VerticalStackLayout
Spacing=&quot;25&quot;
Padding=&quot;30,0&quot;
VerticalOptions=&quot;Start&quot;&gt;
&lt;ListView ItemsSource=&quot;{Binding PatientInfo}&quot;&gt;
&lt;ListView.ItemTemplate&gt;
&lt;DataTemplate&gt;
&lt;TextCell Text=&quot;{Binding Id}&quot;
Detail=&quot;{Binding Name}&quot;
TextColor=&quot;#f35e20&quot;
DetailColor=&quot;#503026&quot; /&gt;
&lt;/DataTemplate&gt;
&lt;/ListView.ItemTemplate&gt;
&lt;/ListView&gt;
&lt;Button
Command=&quot;{ Binding DeleteCommand}&quot;
x:Name=&quot;CounterBtn&quot;
Text=&quot;Click me&quot;
HorizontalOptions=&quot;Center&quot; /&gt;
&lt;/VerticalStackLayout&gt;
&lt;/ContentPage&gt;  

huangapple
  • 本文由 发表于 2023年2月24日 17:36:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/75554868.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定