日志

MVVM 实战之计算器

 来源    2016-12-10    0  

MVVM 实战之计算器

前些日子,一直在学习基于 RxAndroid + Retrofit + DataBinding 技术组合的 MVVM 解决方案。初识这些知识,深深被它们的巧妙构思和方便快捷所吸引,心中颇为激动。但是,“纸上得来终觉浅,绝知此事要躬行”,学习完以后心里还是没有谱,于是,决定自己动手做一个基于这些技术和框架的小应用。

既然是对新技术学习和掌握的练习,因此,摊子不宜铺的太大。经过思量,最终决定使用 DataBinding 技术构建一个小的 MVVM 应用。MVVM 就是 Model-View-ViewModel 的缩写,与 MVC 模式相比,把其中的 Control 更换为 ViewModel 了。MVVM 的特点:ModelView 之间完全没有直接的联系,但是,通过 ViewModelModel 的变化可以反映在 View 上,对 View 操作呢,又可以影响到 Model

平时在编写 Android 应用时,大家都在深受 findViewById 的折磨。DataBinding 还有个好处,就是完全不需要使用 findViewById 来获取控件(当然,需要在布局文件中给控件设置 id 属性)。有了 DataBinding 的支持,在数据变化后,也不需使用代码来改变控件的显示了。这样,我们的代码就清爽多了。

Model


MVVM 中,Model 的变化可以直接反映到 View 上,而不需要通过代码进行设置。这样,就不能用普通的 Java 类型的变量了。Android 专门为这种变量定义了新的变量类型:ObservableXXX

注意:ObservableXXX 是在 android.databinding 包下

变量定义如下:

/** 被操作数 */
public ObservableField<String> firstNum = new ObservableField<>("0");
/** 上一次结果 */
public ObservableField<String> secondNum = new ObservableField<>("");
/** 当前结果 */
public ObservableField<String> resNum = new ObservableField<>("");

  

变量的定义位置应该在 ViewModel 中,后方会有完整代码。

View

布局文件


DataBinding 的布局特点是把正常布局包裹在 layout 节点中,layout 布局中的第一个子直接子元素必须是 data 节点。因为,计算器布局的特点非常符合网格布局的特点,因此,我们选择 GridLayout 控件作为 layout 布局中的第二个直接子元素。布局内容如下:


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="cal"
            type="com.ch.wchhuangya.android.pandora.vm.CalculatorVM"/>
    </data>

    <LinearLayout
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginBottom="10dp"
            android:layout_weight="2"
            android:gravity="bottom"
            android:orientation="vertical"
            >

            <TextView
                android:id="@+id/cal_top_num"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="right"
                android:maxLines="1"
                android:paddingRight="10dp"
                android:text="@{cal.secondNum}"
                android:textColor="#555"
                android:textSize="35sp"
                tools:text="16"
                />

            <TextView
                android:id="@+id/cal_bottom_num"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="right"
                android:maxLines="1"
                android:paddingRight="10dp"
                android:text="@{cal.firstNum}"
                android:textColor="#222"
                android:textSize="45sp"
                tools:text="+ 3234234"
                />

            <TextView
                android:id="@+id/cal_res"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="right"
                android:maxLines="1"
                android:paddingRight="10dp"
                android:text="@{cal.resNum}"
                android:textColor="#888"
                android:textSize="30sp"
                tools:text="= 3234250"
                />

        </LinearLayout>

        <android.support.v7.widget.GridLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="3"
            app:columnCount="4"
            app:orientation="horizontal"
            app:rowCount="5"
            >

            <Button
                android:id="@+id/cal_clear"
                android:layout_marginLeft="5dp"
                android:layout_marginRight="5dp"
                app:layout_rowWeight="1"
                android:text="clear"
                android:onClick="@{() -> cal.clear()}"
                />

            <Button
                android:id="@+id/cal_del"
                android:layout_marginRight="5dp"
                app:layout_rowWeight="1"
                android:text="del"
                android:onClick="@{() -> cal.del()}"
                />

            <Button
                android:id="@+id/cal_divide"
                android:layout_marginRight="5dp"
                app:layout_rowWeight="1"
                android:text="÷"
                android:onClick="@{cal::operatorClick}"
                />

            <Button
                android:id="@+id/cal_multiply"
                app:layout_rowWeight="1"
                android:text="×"
                android:onClick="@{cal::operatorClick}"
                />

            <Button
                android:id="@+id/cal_7"
                android:layout_marginLeft="5dp"
                app:layout_rowWeight="1"
                android:text="7"
                android:onClick="@{cal::numClick}"
                />

            <Button
                android:id="@+id/cal_8"
                app:layout_rowWeight="1"
                android:text="8"
                android:onClick="@{cal::numClick}"
                />

            <Button
                android:id="@+id/cal_9"
                app:layout_rowWeight="1"
                android:text="9"
                android:onClick="@{cal::numClick}"
                />

            <Button
                android:id="@+id/cal_minus"
                app:layout_rowWeight="1"
                android:text="-"
                android:onClick="@{cal::operatorClick}"
                />

            <Button
                android:id="@+id/cal_4"
                android:layout_marginLeft="5dp"
                app:layout_rowWeight="1"
                android:text="4"
                android:onClick="@{cal::numClick}"
                />

            <Button
                android:id="@+id/cal_5"
                app:layout_rowWeight="1"
                android:text="5"
                android:onClick="@{cal::numClick}"
                />

            <Button
                android:id="@+id/cal_6"
                app:layout_rowWeight="1"
                android:text="6"
                android:onClick="@{cal::numClick}"
                />

            <Button
                android:id="@+id/cal_add"
                app:layout_rowWeight="1"
                android:text="+"
                android:onClick="@{cal::operatorClick}"
                />

            <Button
                android:id="@+id/cal_1"
                android:layout_marginLeft="5dp"
                app:layout_rowWeight="1"
                android:text="1"
                android:onClick="@{cal::numClick}"
                />

            <Button
                android:id="@+id/cal_2"
                app:layout_rowWeight="1"
                android:text="2"
                android:onClick="@{cal::numClick}"
                />

            <Button
                android:id="@+id/cal_3"
                app:layout_rowWeight="1"
                android:text="3"
                android:onClick="@{cal::numClick}"
                />

            <Button
                android:id="@+id/cal_equals"
                app:layout_rowSpan="2"
                app:layout_rowWeight="1"
                app:layout_gravity="fill_vertical"
                android:text="="
                android:onClick="@{() -> cal.equalsClick()}"
                />

            <Button
                android:id="@+id/cal_12"
                android:layout_marginLeft="5dp"
                app:layout_rowWeight="1"
                android:text="%"
                android:onClick="@{() -> cal.percentClick()}"
                />

            <Button
                android:id="@+id/cal_zero"
                app:layout_rowWeight="1"
                android:text="0"
                android:onClick="@{cal::numClick}"
                />

            <Button
                android:id="@+id/cal_dot"
                app:layout_rowWeight="1"
                android:text="."
                android:onClick="@{() -> cal.dotClick()}"
                />

        </android.support.v7.widget.GridLayout>

    </LinearLayout>
</layout>

布局文件

布局内容比较简单,下面,只说一些重点:

  1. DataBinding 的布局中,如果需要使用 tools 标签,它的声明必须放在 layout 节点上。否则,布局预览中没有效果

  2. data 节点中申明的是布局文件各元素需要使用到的对象,也可以为对象定义别名

  3. 布局文件中的控件如果要使用 data 中定义的对象,值的类似于:@{View.VISIBLE} 。控件的属性值中,不仅可以使用对象,还能使用对象的方法

Fragment


MVVM 中,ActivityFragment 的作用只是用于控件的初始化,包括控件属性(如颜色)等的设置。因此,它的代码灰常简单,具体如下: 


package com.ch.wchhuangya.android.pandora.view.activity.calculator;

import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.ch.wchhuangya.android.pandora.R;
import com.ch.wchhuangya.android.pandora.databinding.CalculatorBinding;
import com.ch.wchhuangya.android.pandora.vm.CalculatorVM;

/**
 * Created by wchya on 2016-12-07 16:17
 */

public class CalculatorFragment extends Fragment {

    private CalculatorBinding mBinding;
    private CalculatorVM mCalVM;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mBinding = DataBindingUtil.inflate(inflater, R.layout.calculator, container, false);
        mCalVM = new CalculatorVM(getContext());
        mBinding.setCal(mCalVM);
        return mBinding.getRoot();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mCalVM.reset();
    }
}

Fragment

该类中,只有两个方法。

onCreateView 方法用于返回视图,返回的方法与平时使用的 Fragment 略有不同。平时用 View.inflate 方法获取视图并返回,在 DataBinding 下,使用 DataBindingUtil.inflate 方法返回 ViewBinding 对象,然后给该对象对应的布局文件中的变量赋值。

onDestory() 方法中调用了两个释放资源的方法,这两个方法是在 ViewModel 中声明的。

ViewModel


MVVM 中,ViewModel 是重头,它用于处理所有非 UI 的业务逻辑。对于计算器来说,业务逻辑就是数字、符号的输入,数字运算等。具体内容如下:


package com.ch.wchhuangya.android.pandora.vm;

import android.content.Context;
import android.databinding.ObservableField;
import android.view.View;
import android.widget.Button;

/**
 * Created by wchya on 2016-12-07 16:17
 */

public class CalculatorVM extends BaseVM {

    /** 用于定义操作符后的空格显示 */
    public static final String EMPTY_STR = " ";
    /** 用于定义结果数字前的显示 */
    public static final String EQUALS_EMPTY_STR = "= ";

    /** 被操作数 */
    public ObservableField<String> firstNum = new ObservableField<>("0");
    /** 上一次结果 */
    public ObservableField<String> secondNum = new ObservableField<>("");
    /** 当前结果 */
    public ObservableField<String> resNum = new ObservableField<>("");

    /** 被操作数的数值 */
    double fNum;
    /** 上一次结果的数值 */
    double sNum;
    /** 当前结果的数值 */
    double rNum;
    /** 标识当前是否为初始状态 */
    boolean initState = true;
    /** 当前运算符 */
    CalOperator mCurOperator;
    /** 前一运算符 */
    CalOperator mPreOperator;

    /** 运算符枚举 */
    enum CalOperator {
        ADD("+"),
        MINUS("-"),
        MULTIPLY("×"),
        DIVIDE("÷");

        private String value;

        CalOperator(String value) {
            this.value = value;
        }

        /** 根据运算符字符串获取运算符枚举 */
        public static CalOperator getOperator(String value) {
            CalOperator otor = null;
            for (CalOperator operator : CalOperator.values()) {
                if (operator.value.equals(value))
                    otor = operator;
            }
            return otor;
        }
    }

    public CalculatorVM(Context context) {
        mContext = context;
    }

    /**
     * 数字点击处理 
     * 当数字变化时,先变化 firstNum,然后计算结果
     */
    public void numClick(View view) {
        String btnVal = ((Button) view).getText().toString();

        if (btnVal.equals("0")) { // 当前点击 0 按钮
            if (firstNum.get().equals("0")) // 当前显示的为 0
                return;
        }

        String originalVal = firstNum.get();
        boolean firstIsDigit = Character.isDigit(originalVal.charAt(0));

        if (isInitState()) { // 初始状态(既刚打开页面或点击了 Clear 之后)
            handleFirstNum(btnVal, Double.parseDouble(btnVal));
            handleResNum(EQUALS_EMPTY_STR + btnVal, Double.parseDouble(btnVal));
        } else {
            if (firstIsDigit) { // 首位是数字,直接在数字后添加
                String changedVal = originalVal + btnVal;
                handleFirstNum(changedVal, Double.parseDouble(changedVal));
                handleResNum(EQUALS_EMPTY_STR + String.valueOf(fNum), Double.parseDouble(changedVal));
            } else { // 首位是运算符,计算结果后显示

                if (originalVal.length() == 3 && Double.parseDouble(originalVal.substring(2)) == 0L) // 被操作数是 运算符 + 空格 + 0
                    handleFirstNum(mCurOperator.value + EMPTY_STR, Double.parseDouble(btnVal));
                else
                    handleFirstNum(originalVal + btnVal, Double.parseDouble((originalVal + btnVal).substring(2)));

                cal();
            }
        }
        adjustNums();
        setInitState(false);
    }

    /** 退格键事件 */
    public void del() {
        String first = firstNum.get();
        if (secondNum.get().length() > 0) { // 正在计算

            if (first.length() <= 3) { // firstNum 是运算符,把 secondNum 的值赋值给 firstNum,secondNum 清空
                handleFirstNum(sNum + "", sNum);
                handleResNum(EQUALS_EMPTY_STR + secondNum.get(), sNum);
                handleSecondNum("", 0L);
                mCurOperator = null;
            } else { // 把最后一个数字删除,重新计算
                String changedVal = first.substring(0, first.length() - 1);
                handleFirstNum(changedVal, Double.parseDouble(changedVal.substring(2)));
                cal();
            }
        } else { // 没有计算

            if ((first.startsWith("-") && first.length() == 2) || first.length() == 1) { // 只有一位数字
                setInitState(true);
                handleFirstNum("0", 0L);
                handleResNum("", 0L);
            } else {
                String changedFirst = first.substring(0, firstNum.get().length() - 1);
                handleFirstNum(changedFirst, Double.parseDouble(changedFirst));
                handleResNum(EQUALS_EMPTY_STR + fNum, fNum);
            }
        }
        adjustNums();
    }

    /** 运算符点击处理 */
    public void operatorClick(View view) {
        String btnVal = ((Button) view).getText().toString();

        // 如果当前有运算符,并且运算符后有数字,把当前运算符赋值给前一运算符
        if (mCurOperator != null && firstNum.get().length() >= 3)
            mPreOperator = mCurOperator;

        mCurOperator = CalOperator.getOperator(btnVal);

        if (secondNum.get().equals("")) { // 1. 没有 secondNum,把 firstNum 赋值给 secondNum,然后把运算符赋值给 firstNum

            handleSecondNum(firstNum.get(), Double.parseDouble(firstNum.get()));
            handleFirstNum(mCurOperator.value + EMPTY_STR, 0L);
        } else { // 2. 有 secondNum
            if (firstNum.get().length() == 2) { // 2.1 只有运算符时,只改变运算符显示,其它不变

                firstNum.set(mCurOperator.value + EMPTY_STR);
            } else { // 2.2 既有运算符,又有 firstNum 和 secondNum 时,计算结果

                if (mPreOperator != null) {
                    mPreOperator = null;

                    handleFirstNum(mCurOperator.value + EMPTY_STR, 0L);
                    handleSecondNum(rNum + "", rNum);
                } else {
                    cal();
                    handleFirstNum(mCurOperator.value + EMPTY_STR, 0L);
                }
            }
        }
        setInitState(false);
        adjustNums();
    }

    /**
     * 点的事件处理
     * 1. 只能有一个点
     * 2. 输入点后,firstNum 的值不变,只改变显示
     */
    public void dotClick() {
        if (firstNum.get().contains("."))
            return;
        else {
            setInitState(false);
            String val = firstNum.get();

            if (!Character.isDigit(val.charAt(0)) && val.length() == 2) {
                handleFirstNum(val + "0.", fNum);
            } else
                handleFirstNum(val + ".", fNum);
        }
    }

    /**
     * 百分号的事件处理
     * 1. 初始状态或刚刚经过 clear 操作时,点击无反应
     * 2. 当 firstNum 为运算符时,点击无反应
     * 3. 其余情况,点击后将 firstNum 乘以 0.01
     */
    public void percentClick() {
        String originalVal = firstNum.get();
        if (isInitState())
            return;
        else if (originalVal.length() == 1 && !Character.isDigit(originalVal.charAt(0)))
                return;
        else {
            fNum = fNum * 0.01;
            if (mCurOperator != null) {
                handleFirstNum(mCurOperator.value + " " + fNum, fNum);
                cal();
            } else {
                handleFirstNum(String.valueOf(fNum), fNum);
                handleResNum(String.valueOf(fNum), fNum);
            }
        }
    }

    /**
     * 等号事件处理
     * 1. 只有 firstNum,不作任何处理
     * 2. 有 secondNum 时,把 secondNum 和 firstNum 的值进行运算,然后把值赋值给 firstNum,清空 secondNum,
     */
    public void equalsClick() {
        if (!secondNum.get().equals("")) {
            cal();
            handleFirstNum(String.valueOf(rNum), rNum);
            handleSecondNum("", 0L);
        }
        adjustNums();
    }

    /** 计算结果 */
    private void cal() {
        switch (mCurOperator) {
            case ADD:
                rNum = sNum + fNum;
                handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
                break;
            case MINUS:
                rNum = sNum - fNum;
                handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
                break;
            case MULTIPLY:
                rNum = sNum * fNum;
                handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
                break;
            case DIVIDE:
                if (fNum == 0L) {
                    rNum = 0L;
                    handleResNum("= ∞", rNum);
                } else {
                    rNum = sNum / fNum;
                    handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
                }
                break;
        }
        adjustNums();
    }

    /**
     * 调整结果,主要将最后无用的 .0 去掉
     */
    private void adjustNums() {
        String ffNum = firstNum.get();
        String ssNum = secondNum.get();
        String rrNum = resNum.get();
        if (ffNum.endsWith(".0")) {
            firstNum.set(ffNum.substring(0, ffNum.length() - 2));
        }
        if (ssNum.endsWith(".0")) {
            secondNum.set(ssNum.substring(0, ssNum.length() - 2));
        }
        if (rrNum.endsWith(".0"))
            resNum.set(rrNum.substring(0, rrNum.length() - 2));
    }

    /** 将计算器恢复到初始状态 */
    public void clear() {
        setInitState(true);

        handleFirstNum("0", 0L);

        handleSecondNum("", 0L);

        handleResNum("", 0L);

        mCurOperator = null;
    }

    /** 处理被操作数的显示和值 */
    private void handleFirstNum(String values, double val) {
        firstNum.set(values);
        fNum = val;
    }

    /** 处理上次结果的显示和值 */
    private void handleSecondNum(String values, double val) {
        secondNum.set(values);
        sNum = val;
    }

    /** 处理本次结果的显示和值 */
    private void handleResNum(String values, double val) {
        resNum.set(values);
        rNum = val;
    }

    public boolean isInitState() {
        return initState;
    }

    public void setInitState(boolean initState) {
        this.initState = initState;
    }

    @Override
    public void reset() {
        // 释放其它资源
        mContext = null;

        // 取掉观察者的注册
        unsubscribe();
    }
}

ViewModel

要注意的是:ObservableXXX 变量值的获取方法为—— variable.get(),设置方法为:variable.set(xxx)

该类有一个父类:BaseVM, 它用于定义一些通用的变量和子类必须实现的抽象方法。内容如下:


package com.ch.wchhuangya.android.pandora.vm;

import android.content.Context;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.List;

import rx.Subscription;

/**
 * Created by wchya on 2016-11-27 20:32
 */

public abstract class BaseVM {

    /** VM 模式中,View 引用的持有 */
    protected AppCompatActivity mActivity;
    /** VM 模式中,View 引用的持有 */
    protected Fragment mFragment;
    /** VM 模式中,上下文引用的持有 */
    protected Context mContext;
    /** 所有用到的观察者 */
    protected List<Subscription> mSubscriptions = new ArrayList<>();

    /** 释放持有的资源引用 */
    public abstract void reset();

    /** 将所有注册的观察者反注册掉 */
    public void unsubscribe() {
        for (Subscription subscription : mSubscriptions) {
            if (subscription != null && subscription.isUnsubscribed())
                subscription.unsubscribe();
        }
    }
}

BaseVM

最终效果如下:


计算器

结束语


本文只是借助计算器这个小应用,把所学的 DataBindingMVVM 的知识使用在实际当中。文中主要使用了 Google 官方 DataBinding 的一些特性,比如为控件设置属性值,为控件绑定事件等。如果读者对这一块内容还不了解,请在官网上查找相关文档进行学习,地址:https://developer.android.com/topic/libraries/data-binding/index.html

笔者在学习时,对官方文档进行了翻译,如果大家对英文文档比较抗拒,可以尝试看一下我的翻译。因为本人能力有限,难免出现错误,欢迎大家用评论的方式告知于我,翻译文档的地址:https://www.cnblogs.com/wchhuangya/p/6031934.html

该应用只是实现了计算器的基本功能,功能不够完善,而且,还有一些缺陷。已知的缺陷有:1. 双精度位数的处理;2. 特别大、特别小数字的显示及处理;这些缺陷只是计算器算法处理上的缺陷,与本文的主题无关,有兴趣的朋友可以将其修改、完善。记着,改好后记得告诉我哦!

路漫漫其修远兮,吾将上下而求索。此话与诸君共勉之!

Electron 实战桌面计算器应用
日志原文地址:http://blog.gdfengshuo.com/article/22/ 前言 Electron 是一个搭建跨平台桌面应用的框架,仅仅使用 JavaScript.HTML 以及 CSS, ...
C# WPF MVVM 实战 – 5- 用绑定,通过 VM 设置 View 的控件焦点
日志本文介绍在 MVVM 中,如何用 ViewModel 控制焦点. 这焦点设置个东西嘛,有些争论.就是到底要不要用 ViewModel 来控制视图的键盘输入焦点.这里不讨论,假设你就是要通过 VM,设置 ...
C# WPF MVVM 实战 – 4 - 善用 IValueConverter
日志IValueConverter,做 WPF 的都应该接触过,把值换成 Visibility .Margin 等等是最常见的例子,也有很多很好的博文解释过用法.本文只是解释一下,MVVM 中一些情景. ...
MVVM实战
日志1.层次依赖 - (UIViewController *)createInitialViewController { self.viewModelServices = [RWTViewModelSer ...
AvalonJS+MVVM实战部分源码
日志轻量级前端MVVM框架avalon,它兼容到 IE6 (其他MVVM框架,KnockoutJS(IE6), AngularJS(IE9), EmberJS(IE8), WinJS(IE9) ),它可以 ...
python小实例——tkinter实战(计算器)
日志一.完美计算器实验一 import tkinter import math import tkinter.messagebox class calculator: #界面布局方法 def __init ...
洗礼灵魂,修炼python(80)--全栈项目实战篇(8)—— 计算器
日志用正则表达式开发一个计算器,计算用户给定的一串带有加减乘除的公式. 要求:不能使用eval转换字符串 分析: 要求简单,就是计算混合运算,但是不能使用eval直接转换,主要就是把整个式子中的小括号优先 ...
玩转Android之MVVM开发模式实战,炫酷的DataBinding!
日志C# 很早就有了MVVM的开发模式,Android手机中的MVVM一直到去年Google的I\O大会上才推出,姗姗来迟.MVVM这中开发模式的优点自不必多说,可以实现视图和逻辑代码的解耦,而且,按照G ...
AI应用开发实战 - 手写算式计算器
日志扩展手写数字识别应用 识别并计算简单手写数学表达式 主要知识点 了解MNIST数据集 了解如何扩展数据集 实现手写算式计算器 简介 本文将介绍一例支持识别手写数学表达式并对其进行计算的人工智能应用的开 ...
从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器
日志原文:从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器 之前时间一直在使用Caliburn. ...
WPF实战案例-MVVM模式下在Xaml中弹出窗体
日志原文:WPF实战案例-MVVM模式下在Xaml中弹出窗体 相信很多学习和开发wpf项目的同学都了解过mvvm模式,同样,在mvvm模式下会有一个不可忽视的问题,就是怎么在xaml中弹出窗体,而不破坏M ...
全面解禁!真正的Expression Blend实战开发技巧 第七章 MVVM初体验-在DataGrid行末添加按钮
日志原文:[全面解禁!真正的Expression Blend实战开发技巧]第七章 MVVM初体验-在DataGrid行末添加按钮 博客更新较慢,先向各位读者说声抱歉.这一节讲解的依然是开发中经常遇到的一种 ...
CleanAOP实战系列--WPF中MVVM自动更新
日志CleanAOP实战系列--WPF中MVVM自动更新 作者: 立地 邮箱: jarvin_g@126.com QQ: 511363759 CleanAOP介绍:https://github.com/J ...
1
[uwp]MVVM模式实战之必应壁纸查看器
日志最近学习MVVM,至于什么是MVVM我也在这儿不多说了,一是关于它的解释解释网上非常多,二是我怕自己讲不清,误导自己没关系,误导别人就不好了.. 好了,废话结束,看是实战...... 这个必应壁纸的d ...
WPF实战案例-MVVM模式下用附加属性在Xaml中弹出窗体
日志嗯..最近回家去了,2个月没写过代码了,面试只能吹牛,基础都忘了,今天回顾一下,分享一篇通过附加属性去处理窗体弹出的情况. 或许老司机已经想到了,通过设置附加属性值,值变更的回调函数去处理窗体弹出,是 ...
Vue.js前端MVVM框架实战篇
日志相信大家对vue.js这个前端框架有了一定的了解.想必也想把Vue急切的运用在项目中,看看它的魅力到底有多大?别急,今天我会满足大家的想法. 我们一起来看看“Webpack+Vue”的开发模式相比以往 ...
Android简易实战教程--第一话《最简单的计算器》
日志转载请注明出处:http://blog.csdn.net/qq_32059827/article/details/51707931 从今天开始,本专栏持续更新Android简易实战类博客文章.和以往专 ...
简单的物流项目实战,WPF的MVVM设计模式(五)
日志开始界面 <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowD ...
简单的物流项目实战,WPF的MVVM设计模式(四)
日志接下来写ViewModels 创建运单的ViewModel类 public class CreateExpressWindowViewModel: NotificationObject { priva ...
简单的物流项目实战,WPF的MVVM设计模式(三)
日志往Services文件里面添加接口以及实现接口 IUserService接口 List<User> GetAllUser(); GetUserService类 ConnectToDatab ...