WPF用MVVM的解决记录

版权声明:本文为博主初学经验,未经博主允许不得转载。

一、前言

  记录在学习与制作WPF过程中遇到的解决方案。

   焦点的控制,键盘事件触发,输入框的数字限制,异步处理,隐藏状态可用状态,自定义属性等等...

二、配置

系统环境:win10

开发工具:Visual Studio 2017

开发语言:C#.WPF (MVVM框架)

三、自问自答

  1.焦点的控制;

  背景:

  焦点的使用一般用于输入框,切换业务功能时,需要焦点定位在指定输入框位置,便于用户操作;使用MVVM框架开发后,对于前端控件的焦点控制不便调用,控件绑定的都是偏向于文本内容和事件,不像windowsFrom那般直接调用控件的焦点属性;

  解决方式:

1)自定义属性;

  下面第6点的说明;

  

2)前端布局所需焦点,后端遍历;

  在Grid的Style样式里面设置好需要布置焦点的触发器;

  后期每次更改焦点前,都把所有焦点触发器设置为false,然后在更改指定的焦点为true;

2.1) 前端xaml代码

<Grid>
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding TxtAFocus}" Value="True">
<Setter Property="FocusManager.FocusedElement"
               Value="{Binding ElementName=TxtA}"/>
</DataTrigger>
<DataTrigger Binding="{Binding TxtBFocus}" Value="True">
<Setter Property="FocusManager.FocusedElement"
               Value="{Binding ElementName=TxtB}"/>
</DataTrigger>
<DataTrigger Binding="{Binding TxtCFocus}" Value="True">
<Setter Property="FocusManager.FocusedElement"
               Value="{Binding ElementName=TxtC}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<StackPanel Margin="">
<StackPanel Orientation="Horizontal">
<TextBox x:Name="TxtA" Text="" Width="" Height=""
          Tag="输入框A..." Style="{StaticResource TxbTrigger}"/>
<TextBox x:Name="TxtB" Text="" Width="" Height="" Margin=""
          Tag="输入框B..." Style="{StaticResource TxbTrigger}"/>
<TextBox x:Name="TxtC" Text="" Width="" Height=""
          Tag="输入框C..." Style="{StaticResource TxbTrigger}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="焦点A" Width="" Height="" Foreground="White"
        Command="{Binding BtnA}" Template="{StaticResource DefaultButton}"/>
<Button Content="焦点B" Width="" Height="" Foreground="White" Margin="20"
        Command="{Binding BtnB}" Template="{StaticResource DefaultButton}"/>
<Button Content="焦点C" Width="" Height="" Foreground="White"
        Command="{Binding BtnC}" Template="{StaticResource DefaultButton}"/>
</StackPanel>
</StackPanel>
</Grid>

2.2) 前端xaml的后台cs代码

DataContext = new FocusAppViewModel();

2.3) 后端ViewModel代码

public class FocusAppViewModel : ViewModelBase
{
public FocusAppViewModel()
{
BtnA = new RelayCommand(() => SetFocusA("A"));
BtnB = new RelayCommand(() => SetFocusA("B"));
BtnC = new RelayCommand(() => SetFocusA("C"));
}
  public bool TxtAFocus
{
get => _txtAFocus;
set
{
var valueFocus = value;
if (valueFocus) ResetTextFocus();
_txtAFocus = valueFocus;
RaisePropertyChanged("TxtAFocus");
}
}
  private bool _txtAFocus; public bool TxtBFocus
{
get => _txtBFocus;
set
{
var valueFocus = value;
if (valueFocus) ResetTextFocus();
_txtBFocus = valueFocus;
RaisePropertyChanged("TxtBFocus");
}
}
  private bool _txtBFocus; public bool TxtCFocus
{
get => _txtCFocus;
set
{
var valueFocus = value;
if (valueFocus) ResetTextFocus();
_txtCFocus = valueFocus;
RaisePropertyChanged("TxtCFocus");
}
}
  private bool _txtCFocus; public RelayCommand BtnA { get; set; }
public RelayCommand BtnB { get; set; }
public RelayCommand BtnC { get; set; } private void SetFocusA(string num)
{
switch (num)
{
case "A": TxtAFocus = true; break;
case "B": TxtBFocus = true; break;
case "C": TxtCFocus = true; break;
default: TxtAFocus = true; break;
}
} private void ResetTextFocus()
{
TxtAFocus = TxtBFocus = TxtCFocus = false;
}
}

2.4) 执行效果

  2. 键盘事件的触发;

  背景:回车事件,内容更改时发生的事件,鼠标双击事件等等绑定事件的操作;

  解决方式:

  (关于这块 主要是在前端做处理,需添加引用System.Windows.Input 和 System.Windows.Interactivity.dll,如果用第三方MVVM框架就不需要这个dll)

1)TextBox输入框的键盘回车Enter事件

<TextBox Text="">
   <!--回车事件-->
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding BtnB}" />
</TextBox.InputBindings>
   <!--文本内容发生更改的事件-->
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding BtnA}" />
</i:EventTrigger>
</i:Interaction.Triggers>
   <!--鼠标双击事件-->
   <i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding DoubleClickTxT}"
                     CommandParameter="{Binding Freight}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
CommandParameter是事件触发时的参数,可不写;
可以用前面RelayCommand的方法写事件,也可以直接用ICommand写事件且事件的方法可以写在get里面,如:
/// 双击输入框事件 [复制当前文字到粘贴板]
public ICommand DoubleClickTxT
{
get
{
return new DelegateCommand<object>((selectItem) =>
{
CopyMsg = Convert.ToString(selectItem);
if (string.IsNullOrWhiteSpace(CopyMsg)) return;
Clipboard.SetDataObject(selectItem);
});
}
}
Clipboard是粘贴板;
string.IsNullOrWhiteSpace判断字符串是否为NULL值或者空值;

2)DataGrid的鼠标双击事件

<DataGrid x:Name="DgvReceiveOrder" ItemsSource="{Binding LstReceiveOrder}">
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
       Command="{Binding DgvDoubleClick}"
       CommandParameter="{Binding ElementName=DgvReceiveOrder,Path=SelectedItem}"/>
</DataGrid.InputBindings>
</DataGrid>

单击事件可以直接写在SelectedItem里面;

  

  3.输入框的限制

  背景:遇到只需要用户输入数字,价格和重量等不需要特殊字符和字母;

  解决方式:

1)用上面讲述的TextChanged作控制,但是不确定是否我调用不当,导致有些bug异常不清楚如何调整;

  这里我就不贴代码了,由于有bug,已经把代码删掉,用下面两种方法实现效果吧;

  

2)由于这个字符限制不涉及业务逻辑代码,可以不用MVVM的模式,用windowsFrom的后台代码模式进行限制;

2.1)前端xaml代码

<TextBox TextChanged="TxtWeightTextChanged" />

2.2)前端xaml的后台cs代码

public void TxtWeightTextChanged(object sender, TextChangedEventArgs e)
{
TextValueChanged(e, TxtWeight);
} public static void TextValueChanged(
  TextChangedEventArgs e, TextBox txtInput, string txType = "double")
{
var change = new TextChange[e.Changes.Count];
e.Changes.CopyTo(change, );
var offset = change[].Offset;
if (change[].AddedLength <= ) return;
if (txType == "int")
{
int num;
if(string.IsNullOrWhiteSpace(txtInput.Text))return;
if (int.TryParse(txtInput.Text, out num)) return;
txtInput.Text = txtInput.Text.Remove(
                    offset, change[].AddedLength);
txtInput.Select(offset, );
}
else if (txType == "double")
{
double num;
if(string.IsNullOrWhiteSpace(txtInput.Text))return;
if (double.TryParse(txtInput.Text, out num)) return;
txtInput.Text = txtInput.Text.Remove(
                    offset, change[].AddedLength);
txtInput.Select(offset, );
}
}

这里利用了 Int 和 Double 的 tryParse 处理;能转换则转换,转换不了 则移除异常的字符;

3)string类型字符作正则表达式限制处理;

3.1)前端xaml代码

<TextBox input:InputMethod.IsInputMethodEnabled="False" 
      Text="{Binding RowNumber}" />

3.2)后台ViewModel代码

public double InputWeight
{
get => _inputWeight;
set
{
_inputWeight = value;
RowNumber = $"{_inputWeight}";
RaisePropertyChanged("Height");
}
}
private double _inputWeight;
//[RowNumber]与[InputWeight]相互结合使用,前端绑定RowNumber,后端处理数据用InputWeight;
//前端要用上双向绑定,且绑定更新:Mode=TwoWay,UpdateSourceTrigger=PropertyChanged;
//建议开启 input:InputMethod.IsInputMethodEnabled="False" 限制不能切换中文输入法;
//input是xmlns:input="clr-namespace:System.Windows.Input;assembly=PresentationCore"
public string RowNumber
{
get => _rowNumber;
set
{
_rowNumber = ExtractDouble(value);
//int类型,就把double置换成int
var isdouble = double.TryParse(_rowNumber, out var raiseNumber);
if (isdouble && $"{InputWeight}" != $"{raiseNumber}")
InputWeight = raiseNumber; //这里赋值真正需要处理的
RaisePropertyChanged("RowNumber");
}
}
private string _rowNumber; /// 判断字符串非doule字符,则提取数字
private string ExtractDouble(string str)
{
if (string.IsNullOrWhiteSpace(str)) return str;
var isdouble = double.TryParse(str, out var _);
if (isdouble) return str;
if (!Regex.IsMatch(str, @"^\d+(\.\d+)?$"))
str = Regex.Replace(str, @"[^\d.\d]", "");
if (str.Split('.').Length > )
str = $"{str.Split('.')[0]}.{str.Split('.')[1]}";
return str;
}

后台处理业务逻辑代码的时候用InputWeight;

毕竟前端的输入是允许空和数字+小数点,但在后台程序处理的时候,double是不接受空值和有小数点但后面没数字这个异常字符的;

  4.异步的处理

  背景:访问站点接口和操作数据库,或者处理一些耗时的业务场景时,会造成前端页面的假死和卡顿,这时候需要运用异步线程处理,业务逻辑代码后台自动跑,前端提供用户继续操作其他事项或者看到进度条有进度变化;

  解决方式:

异步代码用Task

Task.Factory.StartNew(() => TaskSubmit(param)).ContinueWith(task => ContinueSubmit(task.Result));

[ TaskSubmit ] 方法是对站点接口或者对数据库的交互处理;

[ ContinueSubmit ] 方法是在”TaskSubmit”方法执行完毕后再执行的后续操作;

System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
//TODO 使用该语句切换回主UI线程操作界面控件数据;
}));

备注:建议在异步的时候加上 try{ }catch{ } 语句和写日志的语句防止出现异常时 便于查阅情况;

这里不提供详细代码,如需了解则关注后续关于数据库操作的项目代码!

  5.隐藏和可用状态

  背景:遇到业务场景需要,在触发事件的业务逻辑处理完毕后需要隐藏或者设置某些控件不可用,就需要View和ViewModel之间的属性关联;

    解决方式:

贴图,懒得敲代码(大同小异),知道思路就OK;

  6.自定义属性 (略)

    因为使用了自定义属性导致在开发状态中的设计视图不可视,造成开发困恼且暂时未找到解决方案,就不喜欢用自定义属性,也因此该代码暂略,后期有时间再补充;

  7.ComboBox的输入中文拼音和简写的模糊查询;

1) 前端xaml代码

//头部需含引用:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
System.Windows.Interactivity.dll
<ComboBox MinWidth="" Height="" Tag="选择国家..." 
    DisplayMemberPath="Value" Text="{Binding CountryName}"
Style="{StaticResource ComboBoxStyle}"
    IsDropDownOpen="{Binding CountryOpen}"
ItemsSource="{Binding CountryData}"
    SelectedItem="{Binding CountrySelect}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding CountryKeyUp}"
                 CommandParameter="{Binding CountryName}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>

2)ViewModel后台代码

/// 基础数据视图模型:
public class BasicDataViewModel : BindableBase
{
public BasicDataViewModel()
{
    //获取缓存中的国家信息数据
var countryData = new Service.BasicDataCacheManager().AllCountrys
.OrderBy(t => t.FsName).ToList();
    //初始化一个下拉列表模板
var countrys = new List<TemplateViewModel>();
    //遍历数据重新筛选需要的value和key值
foreach (var tmp in countryData)
{
      //调用转换成拼音的方法把中文翻译成拼音
      var pinYinRet = Common.PinYinHelper.ToPinYin(tmp.FsName);
      //SerachCode字段是查询字典
var model = new TemplateViewModel
{
Key = tmp.Id,
Value = tmp.FsName,
SearchCode = new List<string>{tmp.Id,tmp.FsName,tmp.EnName}
};
      //把拼音添加到字典,也可以把英文添加到字典,自由发挥
if (pinYinRet != null)
{
if(pinYinRet.FirstPingYin != null && pinYinRet.FirstPingYin.Any())
model.SearchCode.AddRange(pinYinRet.FirstPingYin);
if(pinYinRet.FullPingYin != null && pinYinRet.FullPingYin.Any())
model.SearchCode.AddRange(pinYinRet.FullPingYin);
}
countrys.Add(model);
}
CountryData = countrys;
_baseCountryData = countrys;
CountrySelect = new TemplateViewModel();
} /// 基础国家数据 【全部】
private readonly List<TemplateViewModel> _baseCountryData; /// 邮递方式ID 对应key
public string CountryId
{
get
{
if (string.IsNullOrWhiteSpace(_countryId)
         && !string.IsNullOrWhiteSpace(CountryName))
{
        //如果key为空而输入框的中文不为空,则匹配出第一条符合的数据
_countryId = _baseCountryData.FirstOrDefault(t =>
                        t.Value == CountryName)?.Key;
}
return _countryId;
}
set => _countryId = value;
}
 private string _countryId; /// 国家中文名称 对应value
public string CountryName
{
get => _countryName;
set
{
_countryName = value;
if (string.IsNullOrWhiteSpace(_countryName))
{
CountryId = string.Empty;
CountrySelect = new TemplateViewModel();
}
RaisePropertyChanged("CountryName");
}
 }
 private string _countryName; /// 国家
public List<TemplateViewModel> CountryData
{
get => _countryData;
set
{
_countryData = value;
RaisePropertyChanged("CountryData");
}
}
 private List<TemplateViewModel> _countryData; /// 选中的国家
public TemplateViewModel CountrySelect
{
get => _countrySelect;
set
{
_countrySelect = value;
if (!string.IsNullOrWhiteSpace(_countrySelect.Key))
{
CountryId = _countrySelect.Key;
CountryName = _countrySelect.Value;
}
RaisePropertyChanged("CountrySelect");
}
}
 private TemplateViewModel _countrySelect; /// 国家下拉内容的显示
public bool CountryOpen
{
get => _countryOpen;
set
{
_countryOpen = value;
RaisePropertyChanged("CountryOpen");
}
}
private bool _countryOpen; /// 国家输入框键盘键入事件
public ICommand CountryKeyUp
{
get
{
return new DelegateCommand<string>((str) =>
{
var matchItem = string.IsNullOrWhiteSpace(str)
? _baseCountryData
: (from item in _baseCountryData
where !string.IsNullOrEmpty(item.Key)
where item.SearchCode.Any(code =>
                  code.ToLower().Contains(str.ToLower()))
select new TemplateViewModel
{
Key = item.Key,
Value = item.Value,
SearchCode = item.SearchCode
}).ToList();
CountryData = matchItem;
CountryOpen = true;
});
}
}
}

使用了多个事件,自行慢慢体会吧,估计以上代码还有优化的余地;

拼音的方法类 和 中英翻译的方法类就不提供了,

从缓存中获取数据的代码也不提供,毕竟涉及到数据库操作;后期段章中再作描述;

3)执行效果

  8.注:该文章代码全在上面,如需详细代码和视频再私信作者!

  9.下篇预告

分页控件的制作,邮件发送,日志代码,excel导入导出等代码的实现过程;

WPF项目学习.二的更多相关文章

  1. Spring Boot 项目学习 (二) MySql + MyBatis 注解 + 分页控件 配置

    0 引言 本文主要在Spring Boot 基础项目的基础上,添加 Mysql .MyBatis(注解方式)与 分页控件 的配置,用于协助完成数据库操作. 1 创建数据表 这个过程就暂时省略了. 2 ...

  2. WPF项目学习.一

    WPF项目搭建 版权声明:本文为博主初学经验,未经博主允许不得转载. 一.前言 记录在学习与制作WPF过程中遇到的解决方案. 使用MVVM的优点是 数据和视图分离,双向绑定,低耦合,可重用行,相对独立 ...

  3. WPF项目学习.三

    工具代码记录 版权声明:本文为博主初学经验,未经博主允许不得转载. 一.前言 记录在学习与制作WPF过程中遇到的解决方案. 分页控件的制作,邮件发送,日志代码,excel导入导出等代码的实现过程: 二 ...

  4. WPF项目学习.四

    信息收录项目 版权声明:本文为博主初学经验,未经博主允许不得转载. 一.前言 记录在学习与制作WPF过程中遇到的解决方案.  需求文案.设计思路.简要数据库结构.简要流程图和明细代码,动图细化每步操作 ...

  5. php开源项目学习二次开发的计划

      开源项目: cms 国内 dedecms cmstop 国外 joomla, drupal 电商 国内 ecshop 国外 Magento 论坛 discuz 博客 wordpress   学习时 ...

  6. WPF Binding学习(二)

    Binding作为数据的桥梁,连通业务逻辑层的对象(源对象)和UI的控件对象(目标对象).在这座桥梁上,我们不仅可以控制在源对象与目标对象是双向通行还是单向通行.还可以控制数据的放行时机,甚至可以在这 ...

  7. android 开源项目学习<二>

    roottools:   RootTools gives Rooted developers easy access to common rooted tools...  https://code.g ...

  8. Spring Boot 项目学习 (四) Spring Boot整合Swagger2自动生成API文档

    0 引言 在做服务端开发的时候,难免会涉及到API 接口文档的编写,可以经历过手写API 文档的过程,就会发现,一个自动生成API文档可以提高多少的效率. 以下列举几个手写API 文档的痛点: 文档需 ...

  9. Spring Boot 项目学习 (三) Spring Boot + Redis 搭建

    0 引言 本文主要介绍 Spring Boot 中 Redis 的配置和基本使用. 1 配置 Redis 1. 修改pom.xml,添加Redis依赖 <!-- Spring Boot Redi ...

随机推荐

  1. TypeScript和Node模块解析策略

    一般我们在模块化编码时,总会导入其它模块,通常我们使用如下语法: import { A } from './a'; // ES6语法 import { A } from 'a'; var A = re ...

  2. POJ 1426 Find The Multiple(数论——中国同余定理)

    题目链接: http://poj.org/problem?id=1426 Description Given a positive integer n, write a program to find ...

  3. phpstudy最新版中php5.6版报错

  4. php表单提交并发送邮件给某个邮箱(示例源码)

    今天老板要求做一个需求,在官网上做一个页面提交的表单,并且当表单点击后,把表单的内容直接提交并通过发送邮件的方式到老板指定的邮箱,下面就分享 一下我的做法 首先建立一个html文档,把页面制作好,并且 ...

  5. Intellij idea 导入 jdbc

    第一步,去官网https://dev.mysql.com/downloads/connector/j/ 下载驱动程序 第二步,解压压缩包,记住路径 第三步,打开你的idea工程,打开Project S ...

  6. xml报错 Parse Fatal Error :在实体引用中,实体名称必须紧跟在'&'后面

    修改jndi配置文件中的密码后,重启tomcat报错如下  实际问题是xml中默认’&’是非法字符,用     &   替代

  7. 【故障】MySQL主从同步故障-Slave_SQL_Running: No

    转自:http://www.linuxidc.com/Linux/2014-02/96945.htm 故障现象:进入slave服务器,运行:mysql> show slave status\G  ...

  8. Redis-安装、启动

    安装Redis 下载redis安装包http://download.redis.io/redis-stable.tar.gz 解压安装包tar xzf redis-stable.tar.gz 安装cd ...

  9. CSS深入理解学习笔记之z-index

    1.z-index基础 z-index含义:指定了元素及其子元素的"z顺序",而"z顺序"可以决定元素的覆盖顺序.z-index值越大越在上面. z-index ...

  10. Python之数学(math)和随机数(random)

    math包包含了最基本的数学运算函数,如果想要更加高级的数学功能,可以使用标准库外的numpy和scipy库,他们不但支持数组和矩阵运算, 还有丰富的数学和物理方程可供使用 random包可以用来生成 ...