前言
這個框架寫到這里,應該有很多同學發現,框架很多地方的細節,其實是違背了MVVM的設計邏輯的。
沒錯,它的確是違背了。
但為什么明知道違背設計邏輯,還要這樣編寫框架呢?
那是因為,我們編寫的是框架,是使用MVVM的概念編寫框架,而並不是要完美的實現MVVM設計。
兩者有什么區別呢?區別就是前者是實戰,后者只是個理念。
在實戰架構中,並不是UI的東西都一定要放在UI層寫,邏輯的東西放在邏輯層寫的。因為,架構的目的是讓程序員更好的寫代碼,而不是讓代碼死死的固定在某一層。
所以,我們在編寫框架時,設計模式中該切割的東西,就不要猶豫的切割。因為,架構師是設計模式的使用者,而不是被使用者。
舉個例子,當你的邏輯全部提取到某一層中以后,你突然發現,該邏輯執行過程中要彈出提示框,但提示框又是屬於UI層的,此時你猶豫了,把提示框移動到邏輯層,不符合設計理念,但不在邏輯層做,開發又很難受。
遇到這樣的情況,我們該怎么做呢?
很簡單,讓設計理念去死吧,不要猶豫,直接把彈出提示框封裝到邏輯層中即可。
現實中,設計邏輯永遠是要向開發邏輯低頭的,因為實戰永遠高於理論。
框架是什么?
框架就是規則,規則在人類社會被稱之為法律;換言之,框架是代碼界的法律。
人類社會建立法律之初,是抱着人人守法,秩序穩定的理想的。
可現實是殘酷的,總有人,因為各種原因,踐踏法律。
事實上,代碼界也一樣,總是會那不守規矩的程序員觸犯法律,他們會讓代碼跨邊界引用類庫,或者拒絕使用接口聲明對象等等。
為什么不能准守規則呢?
因為他們想更快速的完成任務,所以他們不惜觸犯法律,也要拼一次一夜暴富。。。
所以,架構師作為代碼界的人民警察,一定要做好懲治工作。。。
因為,當一個壞代碼出現后,馬上就會有若干個類似的壞代碼出現,猶如劣幣逐良幣一樣,時間一長,框架就會被破壞。
接着好代碼就得依賴着壞代碼寫。
當壞代碼多了到一定程度,好代碼就會變成Bug了。。。
所以,任重道遠,人民警察還需警惕。。。
為什么要編寫數據控件
我們之前編寫的數據控件功能相對單一;完全可以用屬性和事件代替,所以有些同學會覺得,數據控件好像沒什么用。
其實不然,現實中我們要處理的邏輯,並不是簡單的對象屬性一對一綁定就能處理解決的。
我們需要做很多操作,其中也包括UI操作。而數據控件就是用來應對這種復雜的UI操作的。
因為數據控件通過綁定UI控件后,已經將復雜的UI操作,變成了簡單的數據邏輯操作了。
如果沒有數據控件,那當我們實現一個控件聯動時,就得在Xaml.cs文件中處理了。
如果該控件聯動還要觸發數據變化,那我們就又得從Xaml.cs文件中,穿越回ViewModel中處理邏輯了;亦或者,我們直接在Xaml.cs文件中處理數據邏輯。
不論哪種模式,都會將我們好容易做的邏輯層與UI層混淆到一起。而這個問題,並不是一個彈出框那么簡單的UI越界問題,因為它包含了更多復雜的業務邏輯。
數據控件解決這個煩惱。
我們通過數據控件,實現了控件是控件,數據是數據,清晰的,層次分離;並且通過簡潔的綁定,實現了數據變化與控件變化同步。
DataGrid數據控件
DataGrid數據控件可以說是數據控件的精髓了,因為DataGrid相對復雜,不像其他的數據控件那樣功能單一。
所以,當然我們學習了DataGrid數據控件后,就可以更好的理解,數據控件的意義了。
下面我們先看下DataGrid數據控件的代碼:
public class DataGrid<T> : Control<T> { private Action<T> LoadAction = null; public Action<T> SelectCallBack = null; private Func<object, bool> DataFilter = null; #region 分頁 private volatile int _CurrentPage = 1; public int CurrentPage { get { return _CurrentPage; } set { _CurrentPage = value; if (_CurrentPage > PageCount) { _CurrentPage = PageCount; } if (_CurrentPage < 1) { _CurrentPage = 1; } OnPropertyChanged(); } } private int _PageCount = 1; public int PageCount { get { return _PageCount; } set { _PageCount = value; OnPropertyChanged(); } } private int _RecordCount = 0; public int RecordCount { get { return _RecordCount; } set { _RecordCount = value; if (_RecordCount <= SkipNumber) { PageCount = 1; } else { PageCount = int.Parse(Math.Ceiling((double)RecordCount / (double)SkipNumber).ToString()); } if (_CurrentPage > PageCount) { _CurrentPage = PageCount; } OnPropertyChanged(); } } private int _SkipNumber = 30; public int SkipNumber { get { return _SkipNumber; } set { _SkipNumber = value; OnPropertyChanged(); } } private TextBox<string> _JumpTextBox = new TextBox<string>(); public TextBox<string> JumpTextBox { get { return _JumpTextBox; } set { _JumpTextBox = value; OnPropertyChanged(); } } #region 跳頁 public BaseCommand JumpCommand { get { return new BaseCommand(JumpCommand_Executed); } } void JumpCommand_Executed(object send) { int pagenum = 0; if (int.TryParse(JumpTextBox.Text, out pagenum)) { if (pagenum <= PageCount && pagenum > 0) { CurrentPage = pagenum; if (LoadAction != null) { LoadAction(Condition); } } else { MessageBox.Show("請正確填寫跳轉頁數。", "提示信息"); } } else { MessageBox.Show("請正確填寫跳轉頁數。", "提示信息"); } } #endregion #region 上一頁 public BaseCommand PreviousCommand { get { return new BaseCommand(PreviousCommand_Executed); } } void PreviousCommand_Executed(object send) { if (CurrentPage > 1) { CurrentPage -= 1; if (LoadAction != null) { LoadAction(Condition); } } else { MessageBox.Show("已至首頁。", "提示信息"); } } #endregion #region 下一頁 public BaseCommand NextCommand { get { return new BaseCommand(NextCommand_Executed); } } void NextCommand_Executed(object send) { if (CurrentPage < PageCount) { CurrentPage += 1; if (LoadAction != null) { LoadAction(Condition); } } else { MessageBox.Show("已至末頁。", "提示信息"); } } #endregion #endregion private ObservableCollection<T> _ItemsSource = new ObservableCollection<T>(); public ObservableCollection<T> ItemsSource { get { return _ItemsSource; } set { _ItemsSource = value; if (_ItemsSource != null && _ItemsSource.Count > 0 && SelectedItem == null) { SelectedItem = _ItemsSource.First(); } OnPropertyChanged(); } } public void SetItemsSource(List<T> itemSource) { ItemsSource = new ObservableCollection<T>(itemSource); } public T _SelectedItem; public T SelectedItem { get { return _SelectedItem; } set { _SelectedItem = value; if (SelectCallBack != null) { SelectCallBack(_SelectedItem); } OnPropertyChanged(); } } private ICollectionView _ItemsSourceView; public ICollectionView ItemsSourceView { get { _ItemsSourceView = CollectionViewSource.GetDefaultView(_ItemsSource); return _ItemsSourceView; } set { _ItemsSourceView = value; OnPropertyChanged(); } } private T _Condition = (T)Activator.CreateInstance(typeof(T)); public T Condition { get { return _Condition; } set { _Condition = value; OnPropertyChanged(); } } #region 方法 public DataGrid() { } public void BindSource(Action<T> loadAction, T conditionRow = default(T)) { LoadAction = loadAction; if (LoadAction != null) { CurrentPage = 1; LoadAction(conditionRow); } } public void BindSource(Action loadAction) { LoadAction = new Action<T>((obj) => { loadAction(); }); ; if (LoadAction != null) { CurrentPage = 1; LoadAction(default(T)); } } public void ItemsSourceReBind() { BindSource(LoadAction); } public void SelectedItemReBind() { T newitem = (T)Activator.CreateInstance(typeof(T)); List<System.Reflection.PropertyInfo> plist = typeof(T).GetProperties().ToList(); foreach (var propertyInfo in plist) { propertyInfo.SetValue(newitem, propertyInfo.GetValue(SelectedItem)); } SelectedItem = newitem; } public void SetFilter(Func<object, bool> dataFilter) { try { DataFilter = dataFilter; _ItemsSourceView = CollectionViewSource.GetDefaultView(_ItemsSource); _ItemsSourceView.Filter = new Predicate<object>(DataFilter); } catch(Exception ex) { } } public void Refresh() { if (_ItemsSourceView == null) { _ItemsSourceView = CollectionViewSource.GetDefaultView(this.ItemsSource); } _ItemsSourceView.Refresh(); } #endregion }
從代碼中我們可以看到,DataGrid控件不僅包含了基礎屬性,還包含了上一頁,下一頁,刷新,甚至過濾的功能。
下面,我們看下一下DataGrid控件的基礎應用。
Xaml頁面代碼如下:
<DataGrid Margin="5" FontSize="12" ItemsSource="{Binding TestDataGrid.ItemsSource}" AutoGenerateColumns="True" SelectedItem="{Binding TestDataGrid.SelectedItem}" > </DataGrid>
ViewModel頁面代碼如下:
public DataGrid<User> TestDataGrid { get; set; } TestDataProxy proxy = new TestDataProxy(); public VM_PageDataGrid() { TestDataGrid = new DataGrid<User>(); int currentPage = TestDataGrid.CurrentPage; int skipNumber = TestDataGrid.SkipNumber; proxy.GeDataGridData(null, currentPage, skipNumber, (list, count, msg) => { TestDataGrid.SetItemsSource(list); TestDataGrid.RecordCount = count; }); TestDataGrid.SelectCallBack = (user) => { MessageBox(user.Name); }; }
我們可以看到,基礎的DataGrid應用很簡單,只要設置好綁定,然后將讀取的數據賦值給數據控件的ItemSource屬性即可。(這里我們使用SetItemSource方法為ItemSource賦值)
然后我們會發現,只要我們操作數據控件的ItemSource,不論是增加數據,刪除數據,變更數據,頁面都會自動的同步刷新。
DataGrid的中級應用
我們在上面的代碼中可以看到,DataGrid數據控件還包含了分頁功能。那么如何實現分頁功能呢。
很簡單,我們只需要在Xaml頁面多綁定幾個屬性即可實現。
Xaml代碼如下:
<StackPanel DataContext="{Binding TestDataGrid}" Orientation="Horizontal" DockPanel.Dock="Bottom" HorizontalAlignment="Right" Margin="0,10,0,0"> <Button Content="上一頁" Width="60" Command="{Binding PreviousCommand}" Height="20" Margin="20,0,0,0" VerticalAlignment="Top" /> <Button Content="下一頁" Width="60" Command="{Binding NextCommand}" Height="20" Margin="20,0,0,0" VerticalAlignment="Top" /> <TextBlock VerticalAlignment="Center" Text="每頁" Margin="20,0,0,0"></TextBlock> <TextBlock VerticalAlignment="Center" Text="{Binding SkipNumber}" Margin="0,0,0,0"></TextBlock> <TextBlock VerticalAlignment="Center" Text="條"></TextBlock> <TextBlock VerticalAlignment="Center" Text="{Binding CurrentPage}" Margin="20,0,0,0"></TextBlock> <TextBlock VerticalAlignment="Center" Text="/"></TextBlock> <TextBlock VerticalAlignment="Center" Text="{Binding PageCount}"></TextBlock> <TextBlock VerticalAlignment="Center" Text="總記錄數:" Margin="20,0,0,0"></TextBlock> <TextBlock VerticalAlignment="Center" Text="{Binding RecordCount}"></TextBlock> <TextBox VerticalAlignment="Center" Width="40" Height="20" Margin="40,0,0,0" Text="{Binding JumpTextBox.Text}" ></TextBox> <Button Content="GO" Command="{Binding JumpCommand}" Width="40" Height="20" Margin="5,0,0,0" VerticalAlignment="Top" /> </StackPanel> <GroupBox DockPanel.Dock="Top" Header="DataGrid" Margin="10,0,0,0" > <DataGrid Margin="5" FontSize="12" ItemsSource="{Binding TestDataGrid.ItemsSource}" AutoGenerateColumns="True" SelectedItem="{Binding TestDataGrid.SelectedItem}" > </DataGrid> </GroupBox>
這樣我們就實現了分頁功能,代碼很簡單,並且徹底分割了UI和ViewModel。
但是那么復雜的UI,就這樣簡單的被徹底搞定了嗎?
當然是不可能的!UI很復雜,僅僅靠數據控件是無法徹底搞定的。
那么我們應該怎么辦呢?
很簡單,我們去編寫UI控件就好啦。
當然,我們要編寫的UI控件不是普通的UI控件,而是配合數據控件應用的UI控件。
這種定制UI控件在功能上與其他自定義控件是一樣,但好處就在於,編寫方便,易於理解和二次開發。
----------------------------------------------------------------------------------------------------
本篇文章就先講到這了,下一篇文章我們將一起為框架編寫UI控件。
框架代碼已經傳到Github上了,並且會持續更新。
相關文章:
To be continued——DataGrid
Github地址:https://github.com/kiba518/KibaFramework
----------------------------------------------------------------------------------------------------
注:此文章為原創,任何形式的轉載都請聯系作者獲得授權並注明出處!
若您覺得這篇文章還不錯,請點擊下方的【推薦】,非常感謝!