WPF项目学习.二

2021-02-13 05:18

阅读:692

WPF用MVVM的解决记录

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

一、前言

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

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

 

二、配置

系统环境:win10

开发工具:Visual Studio 2017

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

 

三、自问自答

  1.焦点的控制;

  背景:

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

  解决方式:

1)自定义属性;

  这种方法我不贴代码了,第二次重新编辑该文章时再补充关于自定义属性的调用说明;

  

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

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

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

2.1) 前端xaml代码

技术分享图片

"10">
            "Horizontal">
                "TxtA" Text="" Width="100" Height="30" Tag="输入框A..." Style="{StaticResource TxbTrigger}"/>
                "TxtB" Text="" Width="100" Height="30" Margin="5" Tag="输入框B..." Style="{StaticResource TxbTrigger}"/>
                "TxtC" Text="" Width="100" Height="30" Tag="输入框C..." Style="{StaticResource TxbTrigger}"/>
            "Horizontal">
                

 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事件

"">
   "Enter" Command="{Binding BtnB}" />
   "TextChanged"> "{Binding BtnA}" />
      
"MouseDoubleClick">
"{Binding DoubleClickTxT}" CommandParameter="{Binding Freight}"/>


CommandParameter是事件触发时的参数,可不写;
可以用前面RelayCommand的方法写事件,也可以直接用ICommand写事件且事件的方法可以写在get里面,如:
        /// 双击输入框事件 [复制当前文字到粘贴板]
        public ICommand DoubleClickTxT
        {
            get
            {
                return new DelegateCommandobject>((selectItem) =>
                {
                    CopyMsg = Convert.ToString(selectItem);
                    if (string.IsNullOrWhiteSpace(CopyMsg)) return;
                    Clipboard.SetDataObject(selectItem);
                });
            }
        }
Clipboard是粘贴板;

 

2)DataGrid的鼠标双击事件

"DgvReceiveOrder" ItemsSource="{Binding LstReceiveOrder}" >
    "LeftDoubleClick" 
        Command="{Binding DgvDoubleClick}"
        CommandParameter="{Binding ElementName=DgvReceiveOrder, Path=SelectedItem}" />

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

       

  3.输入框的限制

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

  解决方式:

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

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

  

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

2.1)前端xaml代码

"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, 0);
    var offset = change[0].Offset;
    if (change[0].AddedLength 0) 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[0].AddedLength);
        txtInput.Select(offset, 0);
    }
    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[0].AddedLength);
        txtInput.Select(offset, 0);
    }
}

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

 

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

3.1)前端xaml代码

"False" Text="{Binding RowNumber}" />

3.2)后台ViewModel代码

public double InputWeight
{
    get => _inputWeight;
    set
    {
        _inputWeight = value;
        RowNumber = $"{_inputWeight}";
        RaisePropertyChanged("Height");
    }
}
private double _inputWeight;

 //前端暂时没调用,可以把前端绑定的RowNumber更换为这个浏览效果,效果是限制输入的内容是double;
 //[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 > 2)
         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.combox的输入中文拼音和简写的模糊查询;(暂时略,待更新)

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


评论


亲,登录后才可以留言!