MVVM - UI related code in View model - true separation of concerns - c#

I started implementing a MVVM design pattern in an existing WPF c# application. I am completely new and have never used design patterns or dependency injection before. I was looking at the frameworks already available and have adopted MVVM light. I moved the logic from the view to the viewmodel. I have lot of code in the PopulateTestMenu which is related to UI in the view model. It also has calls to the event handlers. How do I take care of this?
In the XAML I have:
<Window DataContext="{Binding Main, Source={StaticResource Locator}}">
<Menu>
<MenuItem Header="Load All History..." Command="{Binding LoadAllHistory}">
In the MainViewModel class I have:
public ICommand LoadAllHistory { get; private set; }
public MainViewModel()
{
LoadAllHistory = new RelayCommand(() => LoadHistoryExecute(), () => true);
}
The code that I moved from my view to the viewmodel:
private void LoadHistoryExecute()
{
try
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "Test History File (*.xml)|*.xml";
ofd.Title = "Open Test History";
ofd.Multiselect = true;
if (ofd.ShowDialog() == true)
{
ThreadPool.QueueUserWorkItem(LoadTestHistoryCallback, ofd.FileNames);
}
}
catch
{
//some code
}
}
private void LoadTestHistoryCallback(object state)
{
try
{
string[] fileNames = (string[])state;
foreach (string fileName in fileNames)
{
bool success = MyApp.Instance.ParseTestHistory(fileName);
string status = success
? String.Format("'{0}' loaded successfully.",
System.IO.Path.GetFileName(fileName))
: String.Format("Failed to load history from '{0}'.",
System.IO.Path.GetFileName(fileName));
Dispatcher.CurrentDispatcher.DynamicInvoke(delegate()
{
Status = status;
});
PopulateTestMenu(new SortedList<int, int>());
}
}
catch
{
//some code
}
}
private void PopulateTestMenu(SortedList<int, int> indexes)
{
try
{
_testMenuMutex.WaitOne();
//Populate the Tests menu with the list of tests.
Dispatcher.CurrentDispatcher.DynamicInvoke(delegate()
{
menuTests.Items.Clear();
var checkEventHandler = new RoutedEventHandler(testMenuItem_Checked);
bool added = false;
if (MyApp.Instance.TestHistory != null &&
MyApp.Instance.TestHistory.Count > 0)
{
List<ushort> subIds = new
List<ushort>MyApp.Instance.TestHistory.Keys);
foreach (ushort subId in subIds)
{
MenuItem menuItem = null;
menuItem = new MenuItem();
menuItem.Header = subId.ToString().PadLeft(5, '0');**
MenuItem none = new MenuItem();
none.Header = "None";
none.IsCheckable = true;
none.IsChecked = true;
none.Checked += checkEventHandler;
none.Unchecked += checkEventHandler;
menuItem.Items.Add(none);
if (MyApp.Instance.TestHistory != null &&
MyApp.Instance.TestHistory.ContainsKey(subId))
{
var tests = MyApp.Instance.TestHistory[subId];
if (tests != null)
{
foreach (Test t in tests)
{
MenuItem item = new MenuItem();
item.IsCheckable = true;
string description = t.Description.Replace("\n",
"\n".PadRight(34, ' '));
string header = abc;
item.Header = header;
item.DataContext = t;
item.Checked += checkEventHandler;
item.Unchecked += checkEventHandler;
menuItem.Items.Add(item);
}
if (tests.Count > 0)
{
menuTests.Items.Add(menuItem);
added = true;
}
}
}
// Carry over the previous selection.
if (indexes.ContainsKey(subId) && indexes[subId] > -1)
{ ((MenuItem)menuItem.Items[indexes[subId]]).IsChecked =
true;
}
}
}

I am still trying to figure out what you are asking =)...
But you are mixing up some things... Remember one of the core concepts of MVVM is to make the viewmodel testable and remove all view related code off from the viewmodel. So no dependencies to WPF at all. So MenuItem looks like a WPF MenuItem and should not be in your ViewModel.
Instead you could consider to make a MenuItemViewModel which binds to the MenuItem in the View. And it I could see an ObservableCollection<MenuItemViewModel> TestMenu instead of your sorted list.
In your method LoadTestHistoryCallback you would instanciate (could be done via DI) the MenuItemViewModel and add it to the TestMenu Collection. The MenuItemViewModel could have status property which could be assigned from outside or internaly. (It can also have some additional logic, hey its a viewmodel).
In the View you could then bind it to a list with a template representing the MenuItem via DataBinding.
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MenuItems}" />
So remember ViewModel can also contain ViewModels or collections of viewmodel.
Use the rich databinding api from WPF.
Work with bindable Properties like ObservebaleCollections or Properties that are extended with PropertyChanged notification.
HTH
P.S: You can then have a click ICommand in the MenuItemViewModel and execute actions or better use the EventAggregator or Messenger to notify other ViewModels ...(but that's a story for another question =)... )

You have applied the theory of MVVM correctly by moving that code to the ViewModel however just keep in mind that the View should only provide the "structure" of the display.
What is displayed is provided by the model in the ViewModel.
With that in mind separate out the menu parts from the ViewModel method and put them in the View, but leave the Test object creation parts (Binding ViewModel objects to View structure is what it's about).
Within your PopulateTestMenu method the menus and menu structure need to be specified in the View while the data populating them needs to be created and formatted in the ViewModel.
In the View you will bind the appropriate object parts to the menu structure, and the ViewModel will automatically fill it in with the model objects when the model is bound to the view.
Looking at the code, it appears that your Test object is your ViewModel, and the Menu and MenuItem structure needs to be created in the View, then you specify the binding of the specific properties of the Test object to the specific structure parts of the Menu within the View.

Related

Update a combobox from a presenter (MVP)

I am using MVP in my project, but i am new in MVP.
I have two comboboxes. When I select an option in a combobox, the another combobox should be filled with new data.
This action will be in Presenter. I get my view 'view1' in Presenter, and introduced Combobox1 and Combobox2 as properties in 'view1', because I need 'DataSource', 'DisplayMember', 'ValueMember' and 'Refresh()' in the method below.
But, when using a pattern, it is enough to send a property like
public string Combobox2
{
get { return comboBox1.SelectedValue.ToSstring(); }
}
into Presenter not the whole Combobox. How can I solve this problem?
public void OnSelectedIndexChangedCombobox1()
{
if (view1.Combobox1.SelectedIndex == -1)
{
return;
}
DataTable dt = Tools.GetDataTable("A Path");
var query =
(from o in dt.AsEnumerable()
where o.Field<string>("afield") ==
farmerView.Combobox1.SelectedValue.ToString()
orderby o.Field<string>("anotherfield")
select new KeyValuePair<string, string>(o.Field<string>("field1"),
o.Field<string>("field2"))).ToList();
farmerView.Combobox2SelectedIndexChanged -= OnSelectedIndexChangedCombobox2;
farmerView.Combobox2.DataSource = new BindingSource(query, null);
farmerView.Combobox2.DisplayMember = "Value";
farmerView.Combobox2.ValueMember = "Key";
farmerView.Combobox2.Refresh();
farmerView.Combobox2SelectedIndexChanged +=
OnSelectedIndexChangedCombobox2;
farmerView.Combobox2.SelectedIndex = -1;
}
Thank you
You should not pass any Android objects to presenter, just get the event in view (for instance your Activity) then call a method from presenter that provides data for second ComboBox (we call it Spinner in Android!) by passing selected item from first one, and then presenter will call a method of View which fill the second one and View knows how to do it.
You can take a look at this sample project http://github.com/mmirhoseini/marvel and this article https://hackernoon.com/yet-another-mvp-article-part-1-lets-get-to-know-the-project-d3fd553b3e21 to get more familiar with MVP.

How to use Windows.Devices.Enumeration.DevicePicker with Prism (MVVM)?

I am working on a new UWP application that interacts with some hardware via Bluetooth. Using the windows-universal-samples repo as a guide I was able to sucessfully get what I wanted working.
Now I am trying to refactor the code I wrote in a click event handler into a view model class using Prism. However I don't know how to approach this. In other scenarios where I need to pass data between a View and ViewModel I would create a property on the ViewModel and bind it to the control in the view's XAML.
The problem is that Windows.Devices.Enumaration.DevicePicker is used in a way that doesn't seem compatible with the MVVM pattern. In the click handler, the data and control are merged together and I don't see how I can make some kind of list property on the view model and then bind it to the view. Here is the simplest example of the code I am working with:
async void DiscoverButton_OnClick(object sender, RoutedEventArgs e)
{
var devicePicker = new DevicePicker();
devicePicker.Filter.SupportedDeviceSelectors.Add(BluetoothLEDevice.GetDeviceSelectorFromPairingState(true));
// Calculate the position to show the picker (right below the buttons)
var ge = DiscoverButton.TransformToVisual(null);
var point = ge.TransformPoint(new Point());
var rect = new Rect(point, new Point(100, 100));
var device = await devicePicker.PickSingleDeviceAsync(rect);
var bluetoothLEDevice = await BluetoothLEDevice.FromIdAsync(device.Id);
}
See PickSingleDeviceAsync() creates a control directly.
Now I am trying to refactor the code I wrote in a click event handler into a view model class using Prism. However I don't know how to approach this.
You could bind command for your button and use CommandParameter to pass parameter to the command.
Please refer to the following code sample for details:
<Button x:Name="btn" Content="device" Command="{Binding ClickCommand}" CommandParameter="{Binding ElementName=btn}"></Button>
public class MianViewModel : BindableBase
{
public ICommand ClickCommand
{
get;
private set;
}
public MianViewModel()
{
ClickCommand = new DelegateCommand<object>(ClickedMethod);
}
private async void ClickedMethod(object obj)
{
var devicePicker = new DevicePicker();
devicePicker.Filter.SupportedDeviceSelectors.Add(BluetoothLEDevice.GetDeviceSelectorFromPairingState(true));
// Calculate the position to show the picker (right below the buttons)
Button DiscoverButton = obj as Button;
if (DiscoverButton != null)
{
var ge = DiscoverButton.TransformToVisual(null);
var point = ge.TransformPoint(new Point());
var rect = new Rect(point, new Point(100, 100));
var device = await devicePicker.PickSingleDeviceAsync(rect);
if (device != null)
{
var bluetoothLEDevice = await BluetoothLEDevice.FromIdAsync(device.Id);
}
}
}
}
The solution I came up with was to abandon the built in UI provided by DevicePicker and instead create my own UI to use with DeviceWatcher. For example:
void StartWatcher()
{
ResultCollection.Clear();
string selector = BluetoothLEDevice.GetDeviceSelector();
DeviceWatcher = DeviceInformation.CreateWatcher(selector);
DeviceWatcher.Added += async (deviceWatcher, deviceInformation) =>
{
await OnUiThread(() =>
{
ResultCollection.Add(deviceInformation);
});
};
DeviceWatcher.Start();
}
Where ResultCollection would be bound from the view model to the view.

Windows Phone 8.1 ListView.ReorderMode not notifying ObservableCollection

In a Windows Phone 8.1 application (targeting Runtime not Silverlight), I have an ObservableCollection bound to a ListView, defined like this:
<ListView ItemsSource="{Binding ListItems, Mode=TwoWay}" CanReorderItems="True" ReorderMode="Enabled">
<ListView.ItemTemplate>
...etc...
In the ViewModel's constructor, I also have
ListItems.CollectionChanged += ListItems_CollectionChanged;
which is raising the event whenever any items are added and deleted - however, that's all handled from the VM not the View. Unfortunately, the event is not being raised when the items are reorder. I've set this up, moved some items, added a breakpoint in the event handler and added an item, and I can see the underlying ObservableCollection's order has changed as controlled from the View. So why isn't the event raising? And if it won't, what's the best practice for persisting a ListView's order in the database?
UPDATE:
The problem's actually bigger than I thought... it seems that the ListView.CollectionChanged event is not firing when adding an item either! It does when the application starts and loads them from the database but not when added by a user from the UI. This is very strange because the addition of items is performed using the exact same method. From the database:
private ViewModel MapFromModel(Item model, SQLiteAsyncConnection connection)
{
var viewModel = new ViewModel
{
Id = model.Id,
Text = model.Text,
Description = model.Description,
Added = model.Added,
Completed = model.Completed,
DueOn = model.DueOn,
ParentId = model.ParentId,
DisplayOrderNumber = model.DisplayOrderNumber,
IsNew = false
};
foreach (
var childViewModel in
connection.Table<Item>()
.Where(ci => ci.ParentId == viewModel.Id)
.ToListAsync()
.Result.Select(childItem => MapFromModel(childItem, connection)))
{
if (!_cache.Contains(childViewModel))
_cache.Add(childViewModel);
viewModel.AddItem(childViewModel);
}
return viewModel;
}
You see this recursive method calls the ViewModel's AddItem() method to add children (which are of the same type). I also have an ICommand bound to a button to add other items:
public void Execute(object parameter)
{
var viewModel = parameter as ViewModel;
if (viewModel == null) return;
AddItem(viewModel);
}
public static void AddItem(ViewModel viewModel)
{
// The DisplayOrderNumber of the new item needs to be the max of the current collection + 1.
var displayOrderNumber = viewModel.ListItems.Any()
? viewModel.ListItems.Max(ci => ci.DisplayOrderNumber) + 1
: 0;
var newText = string.Format("{0} {1}",
viewModel.Id == Guid.Empty ? "List" : "Item", displayOrderNumber + 1);
var newItem = new ViewModel
{
Text = newText,
NewText = newText,
ParentId = viewModel.Id,
InEditMode = true,
Added = DateTime.Now,
DisplayOrderNumber = displayOrderNumber,
IsNew = true
};
viewModel.AddItem(newItem);
viewModel.Save();
}
So why should the AddItem() method raise the event when called from the Service Layer but not from the ViewModel layer itself?
It turns out, as usual, the fault was entirely mine.
This was occurring because the event handling method ListItems_CollectionChanged was being disconnected. The cause of this was because of a Sort method that was replacing the underlying connection. I've got around this problem by adding the event handler, if needed, in the setter for the property (and of course removing any unused event handlers).

Selected Item From ViewModel to Update using EF

I'm using WPF to create an application to enable an organisation to enter different pieces of data into the application.I have a tab control to allow them to do this.
Then in a separate view, I have a series of different data grids showing the user what data they have inserted into the database. Containing buttons to either, add, update or delete the data they want.
Which leads me to my question. Currently, I am able to delete, and add data with ease and with no problem. But then comes my issue with trying to get the selected item to update, which it doesn't, resulting in a null reference exception.
If i set my property attributes programmatically though, it updates it fine. like so;public int _OrganisationTypeDetailID = 17; public int _OrganisationTypeID = 1;But I do not want this, as I want the ability for the user to select for themselves and update the data they need to.
Here's some of the code that may help in resolving my issue;
View Model;
public void UpdateOrganisationTypeDetail(OrganisationTypeDetail orgTypeDetail)
{
using (DBEntities context = new DBEntities())
{
var orgTD = context.OrganisationTypeDetails.Where(otd => otd.OrganisationTypeDetailID == SelectedType.OrganisationTypeDetailID).FirstOrDefault();
if (orgTD != null)
{
orgTD.Title = Title;
orgTD.FirstName = FirstName;
orgTD.Surname = Surname;
orgTD.Position = Position;
orgTD.DateOfBirth = DateOfBirth;
orgTD.Address = Address;
orgTD.Country = Country;
orgTD.Postcode = Postcode;
orgTD.PhoneNumber = PhoneNumber;
orgTD.MobileNumber = MobileNumber;
orgTD.FaxNumber = FaxNumber;
orgTD.Email = Email;
orgTD.NINumber = NINumber;
//context.OrganisationTypeDetails.Attach(orgTD);
context.OrganisationTypeDetails.ApplyCurrentValues(orgTD);
context.SaveChanges();
MessageBox.Show("Updated Organisation Type Details");
}
else
{
MessageBox.Show("Unable to update selected 'Type'.");
}
}
private OrganisationTypeDetail _SelectedType;
public OrganisationTypeDetail SelectedType
{
get
{
return _SelectedType;
}
set
{
if (_SelectedType == value)
return;
_SelectedType = value;
OnPropertyChanged("SelectedType");
}
}
public List<OrganisationTypeDetail> GetOrganisationTypeDetail //Loads data
{
get
{
using (DBEntities context = new DBEntities())
{
var query = from e in context.OrganisationTypeDetails
select e;
return query.ToList<OrganisationTypeDetail>();
}
}
}
private ICommand showUpdateCommand;
public ICommand ShowUpdateCommand //Update command
{
get
{
if (showUpdateCommand == null)
{
showUpdateCommand = new RelayCommand(this.UpdateFormExecute, this.UpdateFormCanExecute); //i => this.UpdateOrganisationTypeDetail()
}
return showUpdateCommand;
}
}
Code behind;
private void btnUpdateOrgTypeDetail_Click(object sender, RoutedEventArgs e)
{
OrganisationTypeDetail selected = dgOrgTypeDetail.SelectedItem as OrganisationTypeDetail;
OrganisationTypeDetailViewModel org = new OrganisationTypeDetailViewModel();
if (selected == null)
MessageBox.Show("You must select a 'Type' before updating.");
else
{
OrganisationTypeDetailUpdateView update = new OrganisationTypeDetailUpdateView();
update.ShowDialog();
org.UpdateOrganisationTypeDetail(selected);
Page_Loaded(null, null);
}
}
xaml;
<DataGrid Name="dgOrgTypeDetail" Height="145" Width="555"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding GetOrganisationTypeDetail}"
SelectedItem="{Binding SelectedType, Mode=TwoWay}">
Hope this issue can be resolved.
I would say that your best bet for this is to use commanding in the MVVM pattern to achieve this..
It looks like you're using a combination of MVVM and code behind and actually creating a new instance of the view model when your click event fires. Try binding the view model to your view once in the code behind of the view as the datacontext and then try updating the selected type..
Also when you're trying to do the update on SelectedType - look at your View using Snoop - see if the SelectedType property is still bound to the view.
ICommand UpdateOrgTypeDetail { get;}
Then in the view model constructor declare new instance
UpdateOrgTypeDetail = new DelegateCommand<object>(ExecuteUpdateOrgTypeDetail, CanExecuteUpdateOrgTypeDetail);
These two delegates will then allow you to click your button (which needs to bind to UpdateOrgTypeDetail)
<Button Command="{Binding UpdateOrgTypeDetail}" />
You should find that the update on the property is done correctly from here.

Dynamically Generate simple Views from ViewModels in WPF?

I'm slowly learning WPF using this article and other resources.
I am focusing on the application logic - defining the model + viewModel, and creating commands that operate on these. I have not yet looked at the view and the .xaml format.
While I am working on the logic, I want to have a view that can render any viewModel I bind to it. The view should
Render any public string properties as text boxes, and bind the text box to the property
Render the name of the property as a label.
Render any public 'Command' property as a button, and bind the button to the command (perhaps only if the command takes no arguments?)
Is something like this possible while maintaing the MVVM design pattern? If so, how would I achieve it? Also, the article suggests to avoid using .xaml codebehind - can this view be implemented in pure xaml?
I don't think it is possible in XAML only. If you want to generate your views in runtime then you have to just use reflection over your ViewModels and generate controls accordingly. If you want to generate views at compile time then you can generate xaml files from your ViewModels at build time with some template engine (like T4 or string template) or CodeDom. Or you can go further and have some metadata format (or even DSL) from which you will generate both models and views and so on. It is up to your app needs.
And also in MVVM code-behind is Ok for visual logic and binding to model/viewmodel that can't be done in XAML only.
I'm not sure this is an appropriate use for a "pure MVVM" approach, certainly not everything is going to be achieved simply by binding. And I'd just throw away the idea of avoiding using code-behind for your "view" here, this is an inherently programmatic task. The one thing you should stick to is giving the ViewModel no knowledge of the view, so that when you replace it with the "real thing" there is no work to do.
But certainly seems reasonable thing to do; it almost sounds more like a debugging visualiser - you may be able to leverage an existing tool for this.
(If you did want to do this in mostly XAML with standard ItemsControls and templates you might write a converter to expose properties of your ViewModel by reflection in some form that you can bind to, a collection of wrapper objects with exposed metadata, but I think ensuring that the properties exposed are properly bindable would be more work than it's worth)
I'm halfway through implementing this now, I hope the following code will help anyone else trying to do this. It might be fun to turn into a more robust library.
AbstractView.xaml:
<UserControl x:Class="MyApplication.View.AbstractView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Name="container">
</StackPanel>
</UserControl>
AbstractView.xaml.cs:
public partial class AbstractView : UserControl
{
public AbstractView()
{
InitializeComponent();
DataContextChanged += Changed;
}
void Changed(object sender, DependencyPropertyChangedEventArgs e)
{
object ob = e.NewValue;
var props = ob.GetType().GetProperties();
List<UIElement> uies = new List<UIElement>();
foreach (var prop in props)
{
if (prop.PropertyType == typeof(String))
uies.Add(makeStringProperty(prop));
else if (prop.PropertyType == typeof(int))
uies.Add(makeIntProperty(prop));
else if (prop.PropertyType == typeof(bool))
uies.Add(makeBoolProperty(prop));
else if (prop.PropertyType == typeof(ICommand))
uies.Add(makeCommandProperty(prop));
else
{
}
}
StackPanel st = new StackPanel();
st.Orientation = Orientation.Horizontal;
st.HorizontalAlignment = HorizontalAlignment.Center;
st.Margin = new Thickness(0, 20, 0, 0);
foreach (var uie in uies) {
if (uie is Button)
st.Children.Add(uie);
else
container.Children.Add(uie);
}
if (st.Children.Count > 0)
container.Children.Add(st);
}
UIElement makeCommandProperty(PropertyInfo prop)
{
var btn = new Button();
btn.Content = prop.Name;
var bn = new Binding(prop.Name);
btn.SetBinding(Button.CommandProperty, bn);
return btn;
}
UIElement makeBoolProperty(PropertyInfo prop)
{
CheckBox bx = new CheckBox();
bx.SetBinding(CheckBox.IsCheckedProperty, getBinding(prop));
if (!prop.CanWrite)
bx.IsEnabled = false;
return makeUniformGrid(bx, prop);
}
UIElement makeStringProperty(PropertyInfo prop)
{
TextBox bx = new TextBox();
bx.SetBinding(TextBox.TextProperty, getBinding(prop));
if (!prop.CanWrite)
bx.IsEnabled = false;
return makeUniformGrid(bx, prop);
}
UIElement makeIntProperty(PropertyInfo prop)
{
TextBlock bl = new TextBlock();
bl.SetBinding(TextBlock.TextProperty, getBinding(prop));
return makeUniformGrid(bl, prop);
}
UIElement makeUniformGrid(UIElement ctrl, PropertyInfo prop)
{
Label lb = new Label();
lb.Content = prop.Name;
UniformGrid u = new UniformGrid();
u.Rows = 1;
u.Columns = 2;
u.Children.Add(lb);
u.Children.Add(ctrl);
return u;
}
Binding getBinding(PropertyInfo prop)
{
var bn = new Binding(prop.Name);
if (prop.CanRead && prop.CanWrite)
bn.Mode = BindingMode.TwoWay;
else if (prop.CanRead)
bn.Mode = BindingMode.OneWay;
else if (prop.CanWrite)
bn.Mode = BindingMode.OneWayToSource;
return bn;
}
}
Pointer: Generate a dynamic DataTemplate as a string tied to the specific VM (Target). Parse it via XamlReader. Plonk it into your app resources in code.
Just an idea.. run with it.. Should be done by some type other than the View or the ViewModel.

Categories