新文章 网摘 文章 随笔 日记

Xamarin.Forms基本知识(实践)


一、Xamarin.Forms的ViewModel与Page的通信方式

一般ViewModel继承自INotifyPropertyChanged

1、数据延迟加载

ViewModel在初始化时不具体加载数据,而是在界面呈现时加载数据,即在页面的protected override void OnAppearing()方法中异步延迟加载数据。

        //初始化
    protected QueryScoreViewModel viewModel;
        public QueryScorePage()
        {
            InitializeComponent();
            viewModel = new QueryScoreViewModel(30087735);
            viewModel.PropertyChanged += ViewModel_PropertyChanged;
            ExamDropDownList.SelectedItem = viewModel.SelectedItem;
            ExamDropDownList.ItemsSource = viewModel.Items;

            BindingContext = viewModel;
        }

    //界面呈现时加载数据
        protected override void OnAppearing()
        {
            base.OnAppearing();

            if (viewModel.Items.Count == 0)
                viewModel.LoadItemsCommand.Execute(null);
        }

 


2、数据绑定

界面控件数据一般采用与ViewModel绑定方式,如果需要提交的数据,要使用双向绑定,即类似:

Text="{Binding UserAnswer, Mode=TwoWay}"

 

3、消息机制


ViewModel中订阅命令消息并执行:

        //向QueryScorePage页面订阅查询成绩的消息
            MessagingCenter.Subscribe<QueryScorePage, string>(this, "QueryScore", async (obj, examId) =>
            {
                var result = await MyExamService.GetScoreAsync(EmployeeId, examId);
                Score = result;
            });

 


UI后台代码向ViewModel发送消息

MessagingCenter.Send(this, "QueryScore", viewModel.SelectedItem.Id);

 


4、命令

ViewModel中定义命令:

        /// <summary>
        /// 加载可供查询的试卷的命令
        /// </summary>
        public Command LoadItemsCommand { get; set; } = new Command(async () => await ExecuteLoadItemsCommand());


        /// <summary>
        /// 执行加载可供选择的试卷的命令
        /// </summary>
        /// <returns></returns>
        async Task ExecuteLoadItemsCommand()
        {
            if (IsBusy)
                return;

            IsBusy = true;

            try
            {
                Items.Clear();
                var items = await MyExamService.GetExamSelectItemsAsync();

                //因为要使用Items的PropertyChange事件,所以不能用Add,Add无法触发
                var newItems = new ObservableCollection<ExamSelectItem>();
                foreach (var item in items)
                {
                    newItems.Add(item);
                }
                Items = newItems;
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            finally
            {
                IsBusy = false;
            }
        }

 

UI中下达执行命令指令 :

viewModel.LoadItemsCommand.Execute(null);

 


命令也可以在Page中绑定,例如:

        <ListView x:Name="ItemsListView"
                ItemsSource="{Binding Items}"
                VerticalOptions="FillAndExpand"
                HasUnevenRows="true"
                RefreshCommand="{Binding LoadItemsCommand}"
                IsPullToRefreshEnabled="true"
                IsRefreshing="{Binding IsBusy, Mode=OneWay}"
                CachingStrategy="RecycleElement"
                ItemSelected="OnItemSelected">

 

5、ViewModel属性值变化事件机制

ViewModel动态加载数据后以发布事件方式通知Page,系统会自动处理绑定

ViewModel中代码:

        ScoreModel score;
        /// <summary>
        /// 成绩
        /// </summary>
        public ScoreModel Score
        {
            get
            {
                return score;
            }
            set
            {
                score = value;
                //发布属性改变的事件
                OnPropertyChanged("Score");
            }
        }

        public IDataStore<Item> DataStore => DependencyService.Get<IDataStore<Item>>();

        bool isBusy = false;
        public bool IsBusy
        {
            get { return isBusy; }
            set { SetProperty(ref isBusy, value); }
        }

        string title = string.Empty;
        public string Title
        {
            get { return title; }
            set { SetProperty(ref title, value); }
        }

        protected bool SetProperty<T>(ref T backingStore, T value,
            [CallerMemberName]string propertyName = "",
            Action onChanged = null)
        {
            if (EqualityComparer<T>.Default.Equals(backingStore, value))
                return false;

            backingStore = value;
            onChanged?.Invoke();
            OnPropertyChanged(propertyName);
            return true;
        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var changed = PropertyChanged;
            if (changed == null)
                return;

            changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

 


UI后台代码:

    //绑定属性值变化的委托
        viewModel.PropertyChanged += ViewModel_PropertyChanged;

        private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Items")
            {
                ExamDropDownList.ItemsSource = viewModel.Items;
            }
            if (e.PropertyName == "Score")
            {
                ScopeDetail.IsVisible = true;
            }
        }

 

二、依赖注入

注册:

        public App()
        {
            InitializeComponent();

            DependencyService.Register<MockDataStore>();
            DependencyService.Register<IExamService, ExamService>();
            MainPage = new SignInPage();
        }

 

使用:

        public IExamService MyExamService => DependencyService.Get<IExamService>();

 


三、HttpClient访问API

 

using Newtonsoft.Json;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace MPhone.Clients
{
    public class Client
    {
        public static HttpClient CreateHttpClient()
        {
            var handler = GetInsecureHandler();
            HttpClient httpClient = new HttpClient(handler);
            httpClient.MaxResponseContentBufferSize = 256000;
            httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
            return httpClient;
        }

        public static async Task<T> PostAsync<T>(string url, object data) where T : class, new()
        {
            try
            {
                var client = CreateHttpClient();
                string content = JsonConvert.SerializeObject(data);
                var buffer = Encoding.UTF8.GetBytes(content);
                var byteContent = new ByteArrayContent(buffer);
                byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                var response = await client.PostAsync(url, byteContent).ConfigureAwait(false);
                string result = await response.Content.ReadAsStringAsync();
                if (response.StatusCode != HttpStatusCode.OK)
                {
                    throw new Exception($"GetAsync End, url:{url}, HttpStatusCode:{response.StatusCode}, result:{result}");
                }
                if (typeof(T) == typeof(string))
                {
                    return (T)(object)content;
                }
                return JsonConvert.DeserializeObject<T>(result);
            }
            catch (WebException ex)
            {
                if (ex.Response != null)
                {
                    string responseContent = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
                    throw new System.Exception($"response :{responseContent}", ex);
                }
                throw;
            }
        }


        public static async Task<T> GetAsync<T>(string url, object data)
        {
            try
            {
                string requestUrl = data == null ? url : $"{url}?{GetQueryString(data)}";
                //requestUrl = "https://www.baidu.com";
                var client = CreateHttpClient();
                var response = await client.GetAsync(requestUrl).ConfigureAwait(false);
                string content = await response.Content.ReadAsStringAsync();
                if (response.StatusCode != HttpStatusCode.OK)
                {
                    throw new Exception($"GetAsync End, url:{url}, HttpStatusCode:{response.StatusCode}, result:{content}");
                }
                if (typeof(T) == typeof(string))
                {
                    return (T)(object)content;
                }
                var result = JsonConvert.DeserializeObject<T>(content);
                return result;
            }
            catch (WebException ex)
            {
                if (ex.Response != null)
                {
                    string responseContent = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
                    throw new Exception($"Response :{responseContent}", ex);
                }
                throw;
            }
        }

        private static string GetQueryString(object obj)
        {
            var properties = from p in obj.GetType().GetProperties()
                             where p.GetValue(obj, null) != null
                             select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString());

            return String.Join("&", properties.ToArray());
        }
        /// <summary>
        /// 如果是本地服务,不使用https
        /// </summary>
        /// <returns></returns>
        private static HttpClientHandler GetInsecureHandler()
        {
            var handler = new HttpClientHandler();
            handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
            {
                if (cert.Issuer.Equals("CN=localhost"))
                    return true;
                return errors == System.Net.Security.SslPolicyErrors.None;
            };
            return handler;
        }
    }
}

 

使用:

    public class ExamService : IExamService
    {
        public async Task<List<ExamSelectItem>> GetExamSelectItemsAsync()
        {
            var result = await Client.GetAsync<List<ExamSelectItem>>(Setting.ApiUrls.GetLearningExamSelectItemsUrl, null);
            return result;
        }

        public async Task<ScoreModel> GetScoreAsync(int employeeId, string examId)
        {
            var result = await Client.GetAsync<ScoreModel>(Setting.ApiUrls.GetLearningScoreUrl,
                new
                {
                    userId = employeeId,
                    examId = examId
                });
            return result;
        }
    }

 

API服务端:

        [Route("v1/GetSelectItems")]
        public JsonResult<ExamSelectItem[]> GetSelectItems()
        {
            return Json(new[]
            {
                new ExamSelectItem
                {
                    Id = Guid.NewGuid().ToString(),
                    Name= "操作工入职考试 A卷"
                },
                new ExamSelectItem
                {
                    Id = Guid.NewGuid().ToString(),
                    Name= "操作工入职考试 B卷"
                }
            });
        }


        [Route("v1/getscore")]
        public JsonResult<ScoreModel> GetScore([FromUri]int userId, string examId)
        {
            return Json(new ScoreModel
            {
                EmployeeNo = "CN30087735",
                Name = "张三",
                ExamName = "操作工入职考试",
                TotalScore = 90,
                Time = new DateTime(2020, 6, 30)
            });
        }

 

四、动态计算ListView的高度(可以用,但效果不理想,还有待研究):

    /// <summary>
    /// 自定义自动计算ListView的高度,但是效果似乎不是很理想
    /// </summary>
    public class AutoSizeFrame : Frame
    {
        public static void SetSize(object sender, EventArgs e, Dictionary<ListView, Dictionary<VisualElement, int>> ListViewFrameDictionary)
        {
            var frame = (VisualElement)sender;
            ((Frame)frame).Padding = 0;
            var listView = (ListView)frame.Parent.Parent;
            var cell = (ViewCell)frame.Parent;

            var renderHeight = cell.RenderHeight;


            var height = (int)GetChildrenHeight(cell.View, 0);

            //如果listview未计算过
            if (!ListViewFrameDictionary.ContainsKey(listView))
            {
                ListViewFrameDictionary[listView] = new Dictionary<VisualElement, int>();
            }
            //获取ListView的当前帧
            if (!ListViewFrameDictionary[listView].TryGetValue(frame, out var oldHeight) || oldHeight != height)
            {
                ListViewFrameDictionary[listView][frame] = height;

                //该ListView的所有帧高度和
                var fullHeight = ListViewFrameDictionary[listView].Values.Sum();
                if ((int)listView.HeightRequest != fullHeight
                    && listView.ItemsSource.Cast<object>().Count() == ListViewFrameDictionary[listView].Count
                    )
                {
                    listView.HeightRequest = fullHeight;
                    listView.Layout(new Rectangle(listView.X, listView.Y, listView.Width, fullHeight));
                }
            }
        }

        /// <summary>
        /// 递归计算子项总高度
        /// </summary>
        /// <param name="element"></param>
        /// <param name="totalHeight"></param>
        /// <returns></returns>
        private static double GetChildrenHeight(View element, double totalHeight)
        {
            if (element.LogicalChildren.Count > 0)
            {
                foreach (var item in element.LogicalChildren)
                {
                    if (item is StackLayout)
                    {
                        var stackLayout = (StackLayout)item;
                        //如果是横向排列的
                        if (stackLayout.Orientation == StackOrientation.Horizontal)
                        {
                            totalHeight += stackLayout.Measure(1000, 1000, MeasureFlags.IncludeMargins).Minimum.Height;
                        }
                        else
                        {
                            totalHeight += GetChildrenHeight((View)item, totalHeight);
                        }
                        continue;
                    }

                    if (item is View)
                    {
                        totalHeight += GetChildrenHeight((View)item, totalHeight);
                        continue;
                    }
                    //如果不是View的(好像还没遇到过)
                }
                return totalHeight;
            }
            var height = element.Measure(1000, 1000, MeasureFlags.IncludeMargins).Minimum.Height;
            return totalHeight + height;
        }
    }

 

使用:

                <ListView x:Name="JudgmentQuestionListView"
                      HasUnevenRows ="True"
                      VerticalOptions="FillAndExpand" 
                      VerticalScrollBarVisibility="Never"
                      ItemsSource="{Binding JudgmentQuestions}">

                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <ViewCell>
                                <uc:AutoSizeFrame SizeChanged="Frame_OnSizeChanged">

 

后台代码:

        public static readonly Dictionary<ListView, Dictionary<VisualElement, int>> ListViewFrameDictionary = new Dictionary<ListView, Dictionary<VisualElement, int>>();

        private void Frame_OnSizeChanged(object sender, EventArgs e)
        {
            AutoSizeFrame.SetSize(sender, e, ListViewFrameDictionary);
        }

 

五、复杂数据绑定

有子母关系的ViewModel,列表数据需要继承自List<T>,例如:

public class ChoiceQuestionsExamListModel:List<ChoiceQuestionAnswerModel>

在ListView中则需要启用分组:

<Label Text="三、多選題" VerticalOptions="Start" HorizontalOptions="Start" />
<ListView x:Name="MultipleCoiceQuestionsListView"
ItemsSource="{Binding MultipleCoiceQuestions}"
IsGroupingEnabled="True"
HasUnevenRows ="True"
VerticalScrollBarVisibility="Never"
VerticalOptions="FillAndExpand" >

<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<uc:AutoSizeFrame SizeChanged="Frame_OnSizeChanged">
<StackLayout>
<!--多选题标题-->
<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="{Binding Question.Index}"/>
<Span Text="、"/>
<Span Text="{Binding Question.Content}"/>
<Span Text="("/>
<Span Text="{Binding SerialNo}"/>
<Span Text=")"/>
</FormattedString>
</Label.FormattedText>
</Label>
<!--多选题答案-->
<input:SelectionView
x:Name="MultipleCoiceQuestionsSelectionView"
ColumnNumber="1"
ChildAdded="MultipleCoiceQuestions_ChildAdded"
SelectionType="CheckBox"
SelectedItems="{Binding UserAnswers, Mode=TwoWay}"
ItemsSource="{Binding AnswerOptions, Mode=TwoWay}"
ItemDisplayBinding="{Binding Content}"/>
</StackLayout>
</uc:AutoSizeFrame>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" IsVisible="False">
<!--由于动态生成复选框需放在ListView头部,所以这里其实没有东西可放,但ListView又不能省略ItemTemplate-->
<Label IsVisible="False"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>


主要代码如下:

ViewModel:

public class ExamModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Dictionary<ListView, Dictionary<VisualElement, int>> ListViewFrameDictionary = new Dictionary<ListView, Dictionary<VisualElement, int>>();

/// <summary>
/// 判断题
/// </summary>
public IList<JudgmentQuestionExamListModel> JudgmentQuestions = new ObservableCollection<JudgmentQuestionExamListModel>()
{
new JudgmentQuestionExamListModel
{
Index=1,
IdInExam=Guid.NewGuid().ToString(),
QuestionId=Guid.NewGuid().ToString(),
SerialNo="000001",
Content="SMT是指表面贴装技术,DIP是指双列直插封装技术"
},
new JudgmentQuestionExamListModel
{
Index=2,
IdInExam=Guid.NewGuid().ToString(),
QuestionId=Guid.NewGuid().ToString(),
SerialNo="000002",
Content="SMT一般贴装的是无引脚或短引线表面组装元器件,而DIP焊接的是直插形式封装的器件。"
},
new JudgmentQuestionExamListModel
{
Index=3,
IdInExam=Guid.NewGuid().ToString(),
QuestionId=Guid.NewGuid().ToString(),
SerialNo="000003",
Content="Panel作业与MS单板作业完全相同。"
},
};
/// <summary>
/// 单选题
/// </summary>
public ObservableCollection<ChoiceQuestionsExamListModel> SingleCoiceQuestions = new ObservableCollection<ChoiceQuestionsExamListModel>
{
new ChoiceQuestionsExamListModel(
"000004",
new QuestionModel
{
Id=Guid.NewGuid().ToString(),
IdInExam=Guid.NewGuid().ToString(),
Enable=true,
SerialNo="000004",
Index=1,
Type=QuestionType.單選題,
Content="关于收入的确认,下列表述不正确的是( )。"
},
new List<ChoiceQuestionAnswerModel>
{
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="A",
Content="对于在合同开始日即满足收入确认条件的合同,企业在后续期间无需对其进行重新评估,除非有迹象表明相关事实和情况发生重大变化;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="B",
Content="对于在合同开始日不满足收入确认条件的合同,企业应当在后续期间对其进行持续评估,以判断其能否满足收入确认条件;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="C",
Content="企业应当在履行了合同中的履约义务,即在客户取得相关商品控制权时确认收入;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="D",
Content="没有商业实质的非货币性资产交换,如果涉及资产的公允价值能够可靠计量,则需确认相关损益。"
}
}),
new ChoiceQuestionsExamListModel(
"000005",
new QuestionModel
{
Id=Guid.NewGuid().ToString(),
IdInExam=Guid.NewGuid().ToString(),
Enable=true,
SerialNo="000005",
Index=2,
Type=QuestionType.單選題,
Content="关于有关财务报表审计的说法中,错误的是()。"
},
//可选答案
new List<ChoiceQuestionAnswerModel>
{
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="A",
Content="财务报表审计的目的是改善财务报表的质量和内涵;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="B",
Content="财务报表审计的基础是独立性和专业性;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="C",
Content="财务报表审计可以有效满足财务报表预期使用者的需求;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="D",
Content="财务报表审计提供的合理保证意味着注册会计师可以通过获取充分、适当的审计证据消除审计风险。"
}
}
),
new ChoiceQuestionsExamListModel("000006",
new QuestionModel
{
Id=Guid.NewGuid().ToString(),
IdInExam=Guid.NewGuid().ToString(),
Enable=true,
SerialNo="000006",
Index=3,
Type=QuestionType.單選題,
Content="下列关于营运资本的说法,正确的是()。"
},
//可选答案
new List<ChoiceQuestionAnswerModel>
{
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="A",
Content="营运资本越多的企业,流动比率越大;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="B",
Content="营运资本越多,长期资本用于流动资产的金额越大;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="C",
Content="营运资本增加,说明企业短期偿债能力提高;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="D",
Content="营运资本越多的企业,短期偿债能力越强。"
}
})

};

/// <summary>
/// 多选题
/// </summary>
public ObservableCollection<ChoiceQuestionsExamListModel> MultipleCoiceQuestions = new ObservableCollection<ChoiceQuestionsExamListModel>
{
new ChoiceQuestionsExamListModel(
"000007",
new QuestionModel
{
Id=Guid.NewGuid().ToString(),
IdInExam=Guid.NewGuid().ToString(),
Enable=true,
SerialNo="000007",
Index=1,
Type=QuestionType.單選題,
Content="关于收入的确认,下列表述不正确的是( )。"
},
new List<ChoiceQuestionAnswerModel>
{
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="A",
Content="对于在合同开始日即满足收入确认条件的合同,企业在后续期间无需对其进行重新评估,除非有迹象表明相关事实和情况发生重大变化;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="B",
Content="对于在合同开始日不满足收入确认条件的合同,企业应当在后续期间对其进行持续评估,以判断其能否满足收入确认条件;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="C",
Content="企业应当在履行了合同中的履约义务,即在客户取得相关商品控制权时确认收入;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="D",
Content="没有商业实质的非货币性资产交换,如果涉及资产的公允价值能够可靠计量,则需确认相关损益。"
}
}),
new ChoiceQuestionsExamListModel(
"000008",
new QuestionModel
{
Id=Guid.NewGuid().ToString(),
IdInExam=Guid.NewGuid().ToString(),
Enable=true,
SerialNo="000008",
Index=2,
Type=QuestionType.單選題,
Content="关于有关财务报表审计的说法中,错误的是()。"
},
//可选答案
new List<ChoiceQuestionAnswerModel>
{
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="A",
Content="财务报表审计的目的是改善财务报表的质量和内涵;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="B",
Content="财务报表审计的基础是独立性和专业性;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="C",
Content="财务报表审计可以有效满足财务报表预期使用者的需求;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="D",
Content="财务报表审计提供的合理保证意味着注册会计师可以通过获取充分、适当的审计证据消除审计风险。"
}
}
),
new ChoiceQuestionsExamListModel("000009",
new QuestionModel
{
Id=Guid.NewGuid().ToString(),
IdInExam=Guid.NewGuid().ToString(),
Enable=true,
SerialNo="000009",
Index=3,
Type=QuestionType.單選題,
Content="下列关于营运资本的说法,正确的是()。"
},
//可选答案
new List<ChoiceQuestionAnswerModel>
{
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="A",
Content="营运资本越多的企业,流动比率越大;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="B",
Content="营运资本越多,长期资本用于流动资产的金额越大;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="C",
Content="营运资本增加,说明企业短期偿债能力提高;"
},
new ChoiceQuestionAnswerModel
{
Id=Guid.NewGuid().ToString(),
Title="D",
Content="营运资本越多的企业,短期偿债能力越强。"
}
})

};

public void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));

}

public class JudgmentQuestionExamListModel
{
/// <summary>
/// 题库中的Id
/// </summary>
public string QuestionId { get; set; }
/// <summary>
/// 题目在试卷中的Id
/// </summary>
public string IdInExam { get; set; }
/// <summary>
/// 题号
/// </summary>
public string SerialNo { get; set; }
/// <summary>
/// 题目
/// </summary>
public string Content { get; set; }

/// <summary>
/// 用户的答案
/// </summary>
public bool UserAnswer { get; set; }

/// <summary>
/// 题目
/// </summary>
public QuestionModel Question { get; set; }
/// <summary>
/// 顺序号
/// </summary>
public int Index { get; set; }
}

/// <summary>
/// 考试页面选择题列表Model
/// </summary>
public class ChoiceQuestionsExamListModel:List<ChoiceQuestionAnswerModel>
{
/// <summary>
/// 题号
/// </summary>
public string SerialNo { get; set; }

/// <summary>
/// 题目
/// </summary>
public QuestionModel Question { get; set; }

/// <summary>
/// 可选答案
/// </summary>
public List<ChoiceQuestionAnswerModel> AnswerOptions { get; set; }
/// <summary>
/// 用户答案(多选)
/// </summary>
public ObservableCollection<ChoiceQuestionAnswerModel> UserAnswers { get; set; }
/// <summary>
/// 用户答案(单选)
/// </summary>
public ChoiceQuestionAnswerModel UserAnswer { get; set; }
public ChoiceQuestionsExamListModel(string serialNo, QuestionModel question, List<ChoiceQuestionAnswerModel> answerOptions) : base(answerOptions)
{
SerialNo = serialNo;
Question = question;
AnswerOptions = answerOptions;
UserAnswers = new ObservableCollection<ChoiceQuestionAnswerModel>();
}
}

public class QuestionModel
{
public string Id { get; set; }
/// <summary>
/// 题目在试卷中的Id
/// </summary>
public string IdInExam { get; set; }
/// <summary>
/// 题号
/// </summary>
public string SerialNo { get; set; }

/// <summary>
/// 题型
/// </summary>
public QuestionType Type { get; set; }
/// <summary>
/// 题目
/// </summary>
public string Content { get; set; }
/// <summary>
/// 是否可用
/// </summary>
public bool Enable { get; set; }
/// <summary>
/// 索引,顺序
/// </summary>
public int Index { get; set; }
}

public class ChoiceQuestionAnswerModel
{
public string Id { get; set; }
/// <summary>
/// 答案的标题,例如A、B、C、D、
/// </summary>
public string Title { get; set; }
/// <summary>
/// 答案的内容
/// </summary>
public string Content { get; set; }
}

/// <summary>
/// 交卷提交到服务端的Model
/// </summary>
public class ExamSubmitModel
{
/// <summary>
/// 试卷Id
/// </summary>
public string ExamId { get; private set; }
/// <summary>
/// 用户Id
/// </summary>
public int EmployeeId { get; private set; }

/// <summary>
/// 判断题答案
/// </summary>
public List<JudgmentQuestionAnswerSubmitModel> JudgmentQuestionAnswers { get; set; }
/// <summary>
/// 单选题答案
/// </summary>
public List<SingleCoiceQuestionAnswerSubmitModel> SingleCoiceQuestionAnswers { get; set; }
/// <summary>
/// 多选题答案
/// </summary>
public List<MultipleQuestionAnswerSubmitModel> MultipleQuestionAnswers { get; set; }
/// <summary>
/// 交卷提交到服务端的Model构造函数
/// </summary>
/// <param name="examId"></param>
/// <param name="employeeId"></param>
public ExamSubmitModel(string examId, int employeeId)
{
ExamId = examId;
EmployeeId = employeeId;
JudgmentQuestionAnswers = new List<JudgmentQuestionAnswerSubmitModel>();
SingleCoiceQuestionAnswers = new List<SingleCoiceQuestionAnswerSubmitModel>();
MultipleQuestionAnswers = new List<MultipleQuestionAnswerSubmitModel>();
}

}
/// <summary>
/// 判断题答案
/// </summary>
public class JudgmentQuestionAnswerSubmitModel
{
/// <summary>
/// 题目在试卷中的Id
/// </summary>
public string IdInExam { get; set; }
/// <summary>
/// 用户答案在可选答案中的Id
/// </summary>
public bool UserAnswer { get; set; }
}
/// <summary>
/// 单选题答案
/// </summary>
public class SingleCoiceQuestionAnswerSubmitModel
{
/// <summary>
/// 题目在试卷中的Id
/// </summary>
public string IdInExam { get; set; }
/// <summary>
/// 用户答案在可选答案中的Id
/// </summary>
public string UserAnswerIdInOptions { get; set; }
}
/// <summary>
/// 多选题答案
/// </summary>
public class MultipleQuestionAnswerSubmitModel
{
/// <summary>
/// 题目在试卷中的Id
/// </summary>
public string IdInExam { get; set; }
/// <summary>
/// 用户答案在可选答案中的Id
/// </summary>
public List<string> UserAnswerIdsInOptions { get; set; }
}

Page.xaml.cs:


[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ExamPage : ContentPage
{
ExamModel viewModel;
public static readonly Dictionary<ListView, Dictionary<VisualElement, int>> ListViewFrameDictionary = new Dictionary<ListView, Dictionary<VisualElement, int>>();

public ExamPage()
{
InitializeComponent();


//数据绑定
BindingContext = viewModel = new ExamModel();
JudgmentQuestionListView.ItemsSource = viewModel.JudgmentQuestions;
SingleCoiceQuestionsListView.ItemsSource = viewModel.SingleCoiceQuestions;
MultipleCoiceQuestionsListView.ItemsSource = viewModel.MultipleCoiceQuestions;


//设置高度
//int i = viewModel.JudgmentQuestions.Count;
//int heightRowList = 90;
//i = (i * heightRowList);
//JudgmentQuestionListView.HeightRequest = i;

//i = viewModel.SingleCoiceQuestions.Count;
//heightRowList = 500;
//i = (i * heightRowList);
//SingleCoiceQuestionsListView.HeightRequest = i;

//i = viewModel.MultipleCoiceQuestions.Count;
//heightRowList = 500;
//i = (i * heightRowList);
//MultipleCoiceQuestionsListView.HeightRequest = i;
}
/// <summary>
/// 交卷
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnSumit_Clicked(object sender, EventArgs e)
{
//读取用户答案
var userAnswers = new ExamSubmitModel("FF03A385-151D-429E-8547-F18DA6B03414", 30087735);
//判断题
foreach (var item in viewModel.JudgmentQuestions)
{
var userAnswer = item.UserAnswer;
userAnswers.JudgmentQuestionAnswers.Add(new JudgmentQuestionAnswerSubmitModel
{
IdInExam = item.IdInExam,
UserAnswer = userAnswer
});
}
//单选题
foreach (var item in viewModel.SingleCoiceQuestions)
{
var userAnswer = item.UserAnswer;
if (userAnswer != null)
{
userAnswers.SingleCoiceQuestionAnswers.Add(new SingleCoiceQuestionAnswerSubmitModel
{
IdInExam = item.Question.IdInExam,
UserAnswerIdInOptions = userAnswer.Id
});
}
else
{
userAnswers.SingleCoiceQuestionAnswers.Add(new SingleCoiceQuestionAnswerSubmitModel
{
IdInExam = item.Question.IdInExam,
UserAnswerIdInOptions = null
});
}
}
//多选题
foreach (var item in viewModel.MultipleCoiceQuestions)
{
var userAnswer = item.UserAnswers;


if (userAnswer != null)
{
userAnswers.MultipleQuestionAnswers.Add(new MultipleQuestionAnswerSubmitModel
{
IdInExam = item.Question.IdInExam,
UserAnswerIdsInOptions = userAnswer.Select(r => r.Id).ToList()
});
}
else
{
userAnswers.MultipleQuestionAnswers.Add(new MultipleQuestionAnswerSubmitModel
{
IdInExam = item.Question.IdInExam,
UserAnswerIdsInOptions = null
});
}

}

//下面是将答案提交到服务器
}

private void Frame_OnSizeChanged(object sender, EventArgs e)
{
AutoSizeFrame.SetSize(sender, e, ListViewFrameDictionary);
}


private void SingleCoiceQuestionsSelectionView_ChildAdded(object sender, ElementEventArgs e)
{
//SelectionView无法设置字体,所以只好在这里设置
var radioButton = ((Plugin.InputKit.Shared.Controls.RadioButton)e.Element);
radioButton.TextFontSize = 14;
}

private void MultipleCoiceQuestions_ChildAdded(object sender, ElementEventArgs e)
{
//SelectionView无法设置字体,所以只好在这里设置
var radioButton = ((Plugin.InputKit.Shared.Controls.RadioButton)e.Element);
radioButton.TextFontSize = 14;
}

}

Page.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:input="clr-namespace:Plugin.InputKit.Shared.Controls;assembly=Plugin.InputKit"
xmlns:xf="clr-namespace:Xamarin.Forms;assembly=XamarinUserControl"
xmlns:uc="clr-namespace:XamarinUserControl;assembly=XamarinUserControl"
mc:Ignorable="d"
x:Class="MPhone.Views.Learning.ExamPage">
<ContentPage.Content>
<ScrollView Padding="10">
<StackLayout>

<Label Text="一、判斷題" VerticalOptions="Start" HorizontalOptions="Start" />
<ListView x:Name="JudgmentQuestionListView"
HasUnevenRows ="True"
VerticalOptions="FillAndExpand"
VerticalScrollBarVisibility="Never"
ItemsSource="{Binding JudgmentQuestions}">

<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<uc:AutoSizeFrame SizeChanged="Frame_OnSizeChanged">
<StackLayout >
<Label Text="{Binding QuestionId}" IsVisible="False"/>
<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="{Binding Index}"/>
<Span Text="、"/>
<Span Text="{Binding Content}"/>
<Span Text="("/>
<Span Text="{Binding SerialNo}"/>
<Span Text=")"/>
</FormattedString>
</Label.FormattedText>
</Label>
<!--以下是改成横向排列控件-->
<StackLayout Orientation="Horizontal">
<!--由于switch控件没有绑定功能,这个label用于监控开关状态并切换文字为“对”或“错”,用于显示-->
<Label Text="错" TextColor="Red">
<Label.Triggers>
<!--事件触发器-->
<DataTrigger TargetType="Label"
Binding="{Binding Source={x:Reference userAnswerSwitch}, Path=IsToggled}"
Value="true">
<Setter Property="Text" Value="对" />
<Setter Property="TextColor" Value="Green" />
</DataTrigger>
</Label.Triggers>
</Label>
<!--由于switch控件没有绑定功能,只能将值绑定到lable,这个label用于监控开关状态并切换值为“true”或“false”,用于提交值
模式为双向绑定,这样当view中改变时,后端也才会跟着改变,默认是单向绑定的-->
<Label Text="{Binding UserAnswer, Mode=TwoWay}" IsVisible="False">
<!--事件触发器-->
<Label.Triggers>
<!--监控userAnswerSwitch的IsToggled值-->
<DataTrigger TargetType="Label"
Binding="{Binding Source={x:Reference userAnswerSwitch}, Path=IsToggled}"
Value="true">
<!--事件触发时,如果IsToggled为true,设置本label的Text为"true"-->
<Setter Property="Text" Value="true" />
</DataTrigger>
<DataTrigger TargetType="Label"
Binding="{Binding Source={x:Reference userAnswerSwitch}, Path=IsToggled}"
Value="false">
<!--事件触发时,如果IsToggled为false,设置本label的Text为"false"-->
<Setter Property="Text" Value="false" />
</DataTrigger>
</Label.Triggers>
</Label>
<Switch x:Name="userAnswerSwitch" />
</StackLayout>
</StackLayout>
</uc:AutoSizeFrame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

 

<Label Text="二、單選題" VerticalOptions="Start" HorizontalOptions="Start" />
<ListView x:Name="SingleCoiceQuestionsListView"
ItemsSource="{Binding SingleCoiceQuestions}"
IsGroupingEnabled="True"
HasUnevenRows ="True"
VerticalScrollBarVisibility="Never"
VerticalOptions="FillAndExpand">

<!--<ListView.Behaviors>
<xf:AutoSizeBehavior />
</ListView.Behaviors>-->

 

<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<uc:AutoSizeFrame SizeChanged="Frame_OnSizeChanged">
<StackLayout>
<!--单选题标题-->
<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="{Binding Question.Index}"/>
<Span Text="、"/>
<Span Text="{Binding Question.Content}"/>
<Span Text="("/>
<Span Text="{Binding SerialNo}"/>
<Span Text=")"/>
</FormattedString>
</Label.FormattedText>
</Label>
<!--单选题答案-->
<input:SelectionView
x:Name="SingleCoiceQuestionsSelectionView"
ColumnNumber="1"
ChildAdded="SingleCoiceQuestionsSelectionView_ChildAdded"
SelectionType="RadioButton"
SelectedItem="{Binding UserAnswer, Mode=TwoWay}"
ItemsSource="{Binding AnswerOptions, Mode=TwoWay}"
ItemDisplayBinding="{Binding Content}"/>
</StackLayout>
</uc:AutoSizeFrame>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" IsVisible="False">
<!--由于动态生成复选框需放在ListView头部,所以这里其实没有东西可放,但ListView又不能省略ItemTemplate-->
<Label IsVisible="False"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

 

 

<Label Text="三、多選題" VerticalOptions="Start" HorizontalOptions="Start" />
<ListView x:Name="MultipleCoiceQuestionsListView"
ItemsSource="{Binding MultipleCoiceQuestions}"
IsGroupingEnabled="True"
HasUnevenRows ="True"
VerticalScrollBarVisibility="Never"
VerticalOptions="FillAndExpand" >

<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<uc:AutoSizeFrame SizeChanged="Frame_OnSizeChanged">
<StackLayout>
<!--多选题标题-->
<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="{Binding Question.Index}"/>
<Span Text="、"/>
<Span Text="{Binding Question.Content}"/>
<Span Text="("/>
<Span Text="{Binding SerialNo}"/>
<Span Text=")"/>
</FormattedString>
</Label.FormattedText>
</Label>
<!--多选题答案-->
<input:SelectionView
x:Name="MultipleCoiceQuestionsSelectionView"
ColumnNumber="1"
ChildAdded="MultipleCoiceQuestions_ChildAdded"
SelectionType="CheckBox"
SelectedItems="{Binding UserAnswers, Mode=TwoWay}"
ItemsSource="{Binding AnswerOptions, Mode=TwoWay}"
ItemDisplayBinding="{Binding Content}"/>
</StackLayout>
</uc:AutoSizeFrame>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" IsVisible="False">
<!--由于动态生成复选框需放在ListView头部,所以这里其实没有东西可放,但ListView又不能省略ItemTemplate-->
<Label IsVisible="False"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

<Button x:Name="BtnSumit" Text="交卷" Clicked="BtnSumit_Clicked"/>

</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>

六、整合上面消息、命令、事件、绑定、延迟加载等的代码:

ViewModel:

using MPhone.Services;
using MPhone.Views.Learning;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace MPhone.ViewModels.Learning
{
public class QueryScoreViewModel : BaseViewModel
{
/// <summary>
/// 员工Id
/// </summary>
public int EmployeeId { get; private set; }
/// <summary>
/// 试卷Id
/// </summary>
public string ExamId { get; set; }


private ObservableCollection<ExamSelectItem> _items = new ObservableCollection<ExamSelectItem>();
/// <summary>
/// 下拉列表,可供查询的试卷
/// </summary>

public ObservableCollection<ExamSelectItem> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged();
}
}
private ExamSelectItem _selectedItem;
private int _selectedIndex;
public ExamSelectItem SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
OnPropertyChanged();
}
}
ScoreModel score;
/// <summary>
/// 成绩
/// </summary>
public ScoreModel Score
{
get
{
return score;
}
set
{
score = value;
//发布属性改变的事件
OnPropertyChanged("Score");
}
}
/// <summary>
/// 加载可供查询的试卷的命令
/// </summary>
public Command LoadItemsCommand { get; set; }
public IExamService MyExamService => DependencyService.Get<IExamService>();

public QueryScoreViewModel(int employeeId)
{
EmployeeId = employeeId;
Title = "查詢成績";

LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());

//向QueryScorePage页面订阅查询成绩的命令
MessagingCenter.Subscribe<QueryScorePage, string>(this, "QueryScore", async (obj, examId) =>
{
var result = await MyExamService.GetScoreAsync(EmployeeId, examId);
Score = result;
});
}
/// <summary>
/// 执行加载可供选择的试卷的命令
/// </summary>
/// <returns></returns>
async Task ExecuteLoadItemsCommand()
{
if (IsBusy)
return;

IsBusy = true;

try
{
Items.Clear();
var items = await MyExamService.GetExamSelectItemsAsync();

//因为要使用Items的PropertyChange事件,所以不能用Add,Add无法触发
var newItems = new ObservableCollection<ExamSelectItem>();
foreach (var item in items)
{
newItems.Add(item);
}
Items = newItems;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
}
}

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

using Xamarin.Forms;

using MPhone.Models;
using MPhone.Services;

namespace MPhone.ViewModels
{
public class BaseViewModel : INotifyPropertyChanged
{
public IDataStore<Item> DataStore => DependencyService.Get<IDataStore<Item>>();

bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}

string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}

protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;

backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}

#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;

changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}

using System;
using System.Collections.Generic;
using System.Text;

namespace MPhone.ViewModels.Learning
{
public class ExamSelectItem
{
public string Id { get; set; }
public string Name { get; set; }
public override string ToString() => Name;
}
}

using System;
using System.Collections.Generic;
using System.Text;

namespace MPhone.ViewModels.Learning
{
public class ScoreModel
{
public string EmployeeNo { get; set; }
public string Name { get; set; }
public string ExamName { get; set; }
public float TotalScore { get; set; }
public DateTime Time { get; set; }
}
}


Services:

using MPhone.ViewModels.Learning;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace MPhone.Services
{
public interface IExamService
{
Task<ScoreModel> GetScoreAsync(int employeeId, string examId);
Task<List<ExamSelectItem>> GetExamSelectItemsAsync();
}
}

using MPhone.Clients;
using MPhone.ViewModels.Learning;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace MPhone.Services
{
public class ExamService : IExamService
{
public async Task<List<ExamSelectItem>> GetExamSelectItemsAsync()
{
var result = await Client.GetAsync<List<ExamSelectItem>>(Setting.ApiUrls.GetLearningExamSelectItemsUrl, null);
return result;
}

public async Task<ScoreModel> GetScoreAsync(int employeeId, string examId)
{
var result = await Client.GetAsync<ScoreModel>(Setting.ApiUrls.GetLearningScoreUrl,
new
{
userId = employeeId,
examId = examId
});
return result;
}
}
}


using Newtonsoft.Json;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace MPhone.Clients
{
public class Client
{
public static HttpClient CreateHttpClient()
{
var handler = GetInsecureHandler();
HttpClient httpClient = new HttpClient(handler);
httpClient.MaxResponseContentBufferSize = 256000;
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
return httpClient;
}

public static async Task<T> PostAsync<T>(string url, object data) where T : class, new()
{
try
{
var client = CreateHttpClient();
string content = JsonConvert.SerializeObject(data);
var buffer = Encoding.UTF8.GetBytes(content);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync(url, byteContent).ConfigureAwait(false);
string result = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"GetAsync End, url:{url}, HttpStatusCode:{response.StatusCode}, result:{result}");
}
return JsonConvert.DeserializeObject<T>(result);
}
catch (WebException ex)
{
if (ex.Response != null)
{
string responseContent = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
throw new System.Exception($"response :{responseContent}", ex);
}
throw;
}
}


public static async Task<T> GetAsync<T>(string url, object data)
{
try
{
string requestUrl = data == null ? url : $"{url}?{GetQueryString(data)}";
//requestUrl = "https://www.baidu.com";
var client = CreateHttpClient();
var response = await client.GetAsync(requestUrl).ConfigureAwait(false);
string content = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"GetAsync End, url:{url}, HttpStatusCode:{response.StatusCode}, result:{content}");
}
var result = JsonConvert.DeserializeObject<T>(content);
return result;
}
catch (WebException ex)
{
if (ex.Response != null)
{
string responseContent = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
throw new Exception($"Response :{responseContent}", ex);
}
throw;
}
}

private static string GetQueryString(object obj)
{
var properties = from p in obj.GetType().GetProperties()
where p.GetValue(obj, null) != null
select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString());

return String.Join("&", properties.ToArray());
}
/// <summary>
/// 如果是本地服务,不使用https
/// </summary>
/// <returns></returns>
private static HttpClientHandler GetInsecureHandler()
{
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
if (cert.Issuer.Equals("CN=localhost"))
return true;
return errors == System.Net.Security.SslPolicyErrors.None;
};
return handler;
}
}
}

Page.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:input="clr-namespace:Plugin.InputKit.Shared.Controls;assembly=Plugin.InputKit"
x:Class="MPhone.Views.Learning.QueryScorePage">
<ContentPage.Content>
<StackLayout Padding="15" Spacing="15">
<Picker Title="請選擇一個試卷:"
x:Name="ExamDropDownList"
TitleColor="Black"
ItemsSource="{Binding Items, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectedIndexChanged="ExamDropDownList_SelectedIndexChanged"
FontSize="15"
ItemDisplayBinding="{Binding Name}"
/>

<Button Text="查询" x:Name="BtnQuery" Clicked="BtnQuery_Clicked"/>

<StackLayout x:Name="ScopeDetail" IsVisible="False">
<Label Text="{Binding Score.EmployeeNo}"/>
<Label Text="{Binding Score.Name}"/>
<Label Text="{Binding Score.ExamName}"/>
<Label Text="{Binding Score.TotalScore}"/>
<Label Text="{Binding Score.Time}"/>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>

Page.xaml.cs:

using MPhone.ViewModels.Learning;
using System;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace MPhone.Views.Learning
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class QueryScorePage : ContentPage
{
protected QueryScoreViewModel viewModel;
public QueryScorePage()
{
InitializeComponent();
viewModel = new QueryScoreViewModel(30087735);
viewModel.PropertyChanged += ViewModel_PropertyChanged;
ExamDropDownList.SelectedItem = viewModel.SelectedItem;
ExamDropDownList.ItemsSource = viewModel.Items;

BindingContext = viewModel;
}

private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Items")
{
ExamDropDownList.ItemsSource = viewModel.Items;
}
if (e.PropertyName == "Score")
{
ScopeDetail.IsVisible = true;
}
}

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

if (viewModel.Items.Count == 0)
viewModel.LoadItemsCommand.Execute(null);
}

private void BtnQuery_Clicked(object sender, EventArgs e)
{
viewModel.Score = null;
if (viewModel.SelectedItem==null)
{
return;
}
MessagingCenter.Send(this, "QueryScore", viewModel.SelectedItem.Id);
}

private void ExamDropDownList_SelectedItemChanged(object sender, Plugin.InputKit.Shared.Utils.SelectedItemChangedArgs e)
{
//由于双向绑定无法取得数据,所以不得不在这里获取
viewModel.SelectedItem = (ExamSelectItem)e.NewItem;
}

private void ExamDropDownList_SelectedIndexChanged(object sender, EventArgs e)
{

}
}
}


API:

using System;
using System.Web.Http;
using System.Web.Http.Results;
using Models.ViewModels.Learning;

namespace Api.Controllers
{
[RoutePrefix("api/Learning")]
public class LearningController : ApiController
{
[Route("v1/getscores")]
public JsonResult<ScoreModel[]> GetScores()
{
return Json(new[]
{
new ScoreModel
{
EmployeeNo = "CN30087735",
Name="张三",
ExamName="操作工入职考试",
TotalScore=90,
Time=new DateTime(2020,6,30)
},
new ScoreModel
{
EmployeeNo = "CN30087736",
Name="李四",
ExamName="操作工入职考试",
TotalScore=85,
Time=new DateTime(2020,6,30)
},
});
}

[Route("v1/GetSelectItems")]
public JsonResult<ExamSelectItem[]> GetSelectItems()
{
return Json(new[]
{
new ExamSelectItem
{
Id = Guid.NewGuid().ToString(),
Name= "操作工入职考试 A卷"
},
new ExamSelectItem
{
Id = Guid.NewGuid().ToString(),
Name= "操作工入职考试 B卷"
}
});
}


[Route("v1/getscore")]
public JsonResult<ScoreModel> GetScore([FromUri]int userId, string examId)
{
return Json(new ScoreModel
{
EmployeeNo = "CN30087735",
Name = "张三",
ExamName = "操作工入职考试",
TotalScore = 90,
Time = new DateTime(2020, 6, 30)
});
}
}
}

 

posted @ 2020-08-21 15:38  岭南春  阅读(602)  评论(0)    收藏  举报