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)
});
}
}
}
浙公网安备 33010602011771号