C# Winforms MVP-Passive View, where to handle the Model? - c#

I am new to the MVP-PV type of pattern and have some questions on how to handle the Models. (I am using simple CRUD statements in the app, not EF) Does the Model contain the CRUD that is used to retrieve data or just contain the properties for the model. Where do you instantiate the Model? In the View passed with the Presenter? At the top of the Presenter? In each method?
Here is a simple example of what I am doing with MVP.
The UserControl is added like this:
ElementView uControl = new ElementView()
uControl.Location = new Point(0, 0);
uControl.Dock = DockStyle.Fill;
rPanel.Controls.Add(uControl);
The Interface:
namespace MPVExample.View
{
public interface IElementView
{
int IElementPKey { get; set; }
string INumber { get; set; }
string IDescription { get; set; }
event EventHandler<EventArgs> OnEditElement;
}
}
The View:
namespace MPVExample.View
{
public partial class ElementView : UserControl, IElementView
{
private ElementPresenter presenter = null;
public event EventHandler<EventArgs> OnEditElement;
public int IElementPKey
{
get { return TxtElementKey.Text; }
set { TxtElementKey.Text = value; }
}
public string INumber
{
get { return TxtNumber.Text; }
set { TxtNumber.Text = value; }
}
public string IDescription
{
get { return TxtDescription.Text; }
set { TxtDescription.Text = value; }
}
public ElementView ()
{
presenter = new ElementPresenter(this);
InitializeComponent();
}
private void BtnEdit_Click(object sender, EventArgs e)
{
OnEditElement?.Invoke(this, EventArgs.Empty);
}
}
}
The Presenter:
namespace MPVExample.Presenter
{
public class ElementPresenter
{
private readonly IElementViewView;
//ElementModel Model = new ElementModel (); //Instantiate Here?
public ElementPresenter(IElementView view)
{
try
{
if (view != null)
{
View = view;
Initialize();
}
else
{
throw new ArgumentNullException("IElementView");
}
}
catch (Exception ex)
{
//Log Error
}
}
private void Initialize()
{
try
{
View.OnEditElement += Edit_Element;
}
catch (Exception ex)
{
//Log Error
}
}
private void Edit_Element(object sender, EventArgs e)
{
try
{
ElementModel model = new ElementModel(); //Instantiate Here?
Model.ElementPKey = View.IElementPKey;
Model.Number = Convert.ToByte(View.INumber);
Model.Description = View.IDescription;
Model.Edit();
}
catch (Exception ex)
{
//Log Error
}
}
}
}
The Model:
namespace MPVExample.Models
{
public class ElementModel
{
public int ElementPKey { get; set; }
public byte Number { get; set; }
public string Description { get; set; }
public void Edit() //Does this belong in here?
{
//SQL to Edit record
}
public void Save() //Does this belong in here?
{
//SQL to Save record
}
public void Get() //Does this belong in here?
{
//SQL to Get record
}
public void Delete() //Does this belong in here?
{
//SQL to Delete record
}
}
}

Models should be initialized within the presenters. It is better to initialize it at a single place and use it everywhere within the presenters. Views can call the presenter methods along with the parameters needed(mainly from GUI elements). Inside presenter methods, with all the parameters passed, you can use it and interact with the model object and perform get, edit, save, delete operations. In case you need to update the change in model to the view, you need to have methods, within the models that call methods in presenter, which will inturn call the methods in view which brings the UI updates.
For beginners, it can be confusing. Simply put, every presenter will have a reference to it's view as well as the model. Every view will have a reference to it's presenter alone. Every model will have reference to presenters alone, not the view. This way you can achieve loose coupling between the various layers of the application.
https://github.com/HewittVS/MVP-Pattern-CSharp
I have attached link to my github repository, containing a sample application using MVP pattern. But I haven't used models much. You can see how far we can change actual UI event methods in terms of presenter methods calls. Good Luck mate :)

Related

How can I define a correct architeture to an user control at Windows Forms with pattern MVP(Passive)?

I chose the pattern MVP(Passive). This is my first implementation in MVP and I have a lot of doubts. I've already looked few sites and watched videos, but it was not enough to give me an idea of how I should proceed.
There are three controls in my view, but only one will be displaying at a time. There's a method called in the presenter ConfigureCurrentWindow(FieldType type) that it is responsable to get the type and update the view to show the right control. Only one control can be visible at a time.
The way that I chose update the view with the methods (ConfigureCurrentWindow & Focus). Did I choose correct implematation or I broke the architeture MVP(Passive)?
In my head there will be two presenters and two views. one presenter for user control and another one the form displayed to the user. Is that way right?
I saw in some sites that should use a event handler to user interactions. why I cannot call a method like this"_presenter.TextChanged()" in my view?
How should I get the value"text" from the right control? Where should I implement a method to get the value?
Model
public interface IInputDialogModel : IModel
{
FieldType Type { get; set; }
string Description { get; set; }
string Text {get;set;}
//I've omitted some properties
void OnValueChanged(Object sender, InputDialogEventArgs e);
event EventHandler<InputDialogEventArgs> ValueChanged;
void OnValueCompleted(Object sender, InputDialogEventArgs e);
event EventHandler<InputDialogEventArgs> ValueCompleted;
}
View
public interface IInputDialogView : IView
{
string Title { get; set; }
string InputText { get; set; }
string Description { get; set; }
bool Required { get; set; }
//I've omitted some properties
event EventHandler<EventArgs> InputValueChanged;
void Focus(FieldType type);
void SetErrorProvider(string message);
void ClearErrorProvider();
}
Concrete View
public partial class InputDialogForm : Form, IInputDialogView
{
private InputDialogModalPresenter _presenter;
#region Interface
public event EventHandler<EventArgs> InputValueChanged;
public string Title { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public bool MultiLine { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public FormBorderStyle BorderStyle { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public string InputText { get => throw new NotImplementedException();
//I've omitted some properties
#endregion
public InputDialogForm(IInputDialogModel inputDialogModel)
{
InitializeComponent();
_presenter = new InputDialogModalPresenter(this, inputDialogModel);
maskDate.TextChanged += txtInput_TextChanged;
}
private void txtInput_TextChanged(object sender, EventArgs e)
{
//InputValueChanged?.Invoke(sender, e);
}
private void InputDialogForm_Shown(object sender, EventArgs e)
{
_presenter.SetFocus();
}
void IInputDialogModalView.Focus(FieldType type)
{
//I must choose which control must be focused according the Model Type???
switch (fieldType)
{
case FieldType.SingleLine:
textboxSingle.Focus();
break;
case FieldType.MultiLine:
textboxMulti.Focus();
break;
case FieldType.Date:
maskedDate.Focus();
break;
}
}
//My principal question is here. I must turn visible the correct panel
//by the Model Type. is this implementation right?
public void ConfigureCurrentWindow(FieldType fieldType)
{
Panel currentPanel = null;
switch (fieldType)
{
case FieldType.SingleLine:
currentPanel = panelSingleTextBox;
break;
case FieldType.MultiLine:
currentPanel = panelMultiTextBox;
break;
case FieldType.Date:
currentPanel = panelDate;
break;
}
currentPanel.Visible = true;
currentPanel.Dock = DockStyle.Fill;
}
}
Presenter
public class InputDialogPresenter : Presenter<IInputDialogView, IInputDialogModel>{
public InputDialogModalPresenter(IInputDialogModalView view, IInputDialogModel model) :base(view , model)
{
abstractFactoryInputDialog =AbstractFactoryInputDialog.GetFactory(view, model);
LoadInputDialog(view, model);
}
protected override void SubscribeEvents()
{
View.InputValueChanged += OnValueChanged;
}
public void Focus()
{
view.Focus(model.Type);
}
public void UpdateView()
{
view.ConfigureCurrentWindow(model.Type);
}}
Image about the UserControl
https://imgur.com/8kJUQdn

Event circularity

I find myself quite often in the following situation:
I have a user control which is bound to some data. Whenever the control is updated, the underlying data is updated. Whenever the underlying data is updated, the control is updated. So it's quite easy to get stuck in a never ending loop of updates (control updates data, data updates control, control updates data, etc.).
Usually I get around this by having a bool (e.g. updatedByUser) so I know whether a control has been updated programmatically or by the user, then I can decide whether or not to fire off the event to update the underlying data. This doesn't seem very neat.
Are there some best practices for dealing with such scenarios?
EDIT: I've added the following code example, but I think I have answered my own question...?
public partial class View : UserControl
{
private Model model = new Model();
public View()
{
InitializeComponent();
}
public event EventHandler<Model> DataUpdated;
public Model Model
{
get
{
return model;
}
set
{
if (value != null)
{
model = value;
UpdateTextBoxes();
}
}
}
private void UpdateTextBoxes()
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateTextBoxes()));
}
else
{
textBox1.Text = model.Text1;
textBox2.Text = model.Text2;
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
model.Text1 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
model.Text2 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void OnModelUpdated()
{
DataUpdated?.Invoke(this, model);
}
}
public class Model
{
public string Text1 { get; set; }
public string Text2 { get; set; }
}
public class Presenter
{
private Model model;
private View view;
public Presenter(Model model, View view)
{
this.model = model;
this.view = view;
view.DataUpdated += View_DataUpdated;
}
public Model Model
{
get
{
return model;
}
set
{
model = value;
view.Model = model;
}
}
private void View_DataUpdated(object sender, Model e)
{
//This is fine.
model = e;
//This causes the circular dependency.
Model = e;
}
}
One option would be to stop the update in case the data didn't change since the last time. For example if the data were in form of a class, you could check if the data is the same instance as the last time the event was triggered and if that is the case, stop the propagation.
This is what many MVVM frameworks do to prevent raising PropertyChanged event in case the property didn't actually change:
private string _someProperty = "";
public string SomeProperty
{
get
{
return _someProperty;
}
set
{
if ( _someProperty != value )
{
_someProperty = value;
RaisePropertyChanged();
}
}
}
You can implement this concept similarly for Windows Forms.
What you're looking for is called Data Binding. It allows you to connect two or more properties, so that when one property changes others will be updated auto-magically.
In WinForms it's a little bit ugly, but works like a charm in cases such as yours. First you need a class which represents your data and implements INotifyPropertyChanged to notify the controls when data changes.
public class ViewModel : INotifyPropertyChanged
{
private string _textFieldValue;
public string TextFieldValue {
get
{
return _textFieldValue;
}
set
{
_textFieldValue = value;
NotifyChanged();
}
}
public void NotifyChanged()
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Than in your Form/Control you bind the value of ViewModel.TextFieldValue to textBox.Text. This means whenever value of TextFieldValue changes the Text property will be updated and whenever Text property changes TextFieldValue will be updated. In other words the values of those two properties will be the same. That solves the circular loops issue you're encountering.
public partial class Form1 : Form
{
public ViewModel ViewModel = new ViewModel();
public Form1()
{
InitializeComponent();
// Connect: textBox1.Text <-> viewModel.TextFieldValue
textBox1.DataBindings.Add("Text", ViewModel , "TextFieldValue");
}
}
If you need to modify the values from outside of the Form/Control, simply set values of the ViewModel
form.ViewModel.TextFieldValue = "new value";
The control will be updated automatically.
You should look into MVP - it is the preferred design pattern for Winforms UI.
http://www.codeproject.com/Articles/14660/WinForms-Model-View-Presenter
using that design pattern gives you a more readable code in addition to allowing you to avoid circular events.
in order to actually avoid circular events, your view should only export a property which once it is set it would make sure the txtChanged_Event would not be called.
something like this:
public string UserName
{
get
{
return txtUserName.Text;
}
set
{
txtUserName.TextChanged -= txtUserName_TextChanged;
txtUserName.Text = value;
txtUserName.TextChanged += txtUserName_TextChanged;
}
}
or you can use a MZetko's answer with a private property

Panel does not contain a constructor that takes 0 arguments

I need to load a User Control in my panel1 inside Form1.cs, the problem is that the UserControl (AudioPlaybackPanel) contains an ImportingConstructor ([ImportMany]IEnumerable<>) and I can't figure out what two arguments I should have in the Form1 AudioPlaybackPanel(????).
The error I get is: 'NAudio.App.AudioPlaybackPanel' does not contain a constructor that takes 0 arguments
Here is the Form1.cs
namespace NAudio.App
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
AudioPlaybackPanel myPanel = new AudioPlaybackPanel(????);
panel1.Controls.Add(myPanel);
}
}
}
And this is my User Control Panel (AudioPlaybackPanel.cs):
namespace NAudio.App
{
[Export]
public partial class AudioPlaybackPanel : UserControl
{
private IWavePlayer waveOut;
private string fileName = null;
private WaveStream fileWaveStream;
private Action<float> setVolumeDelegate;
[ImportingConstructor]
public AudioPlaybackPanel([ImportMany]IEnumerable<IOutputDevicePlugin> outputDevicePlugins)
{
InitializeComponent();
LoadOutputDevicePlugins(outputDevicePlugins);
}
[ImportMany(typeof(IInputFileFormatPlugin))]
public IEnumerable<IInputFileFormatPlugin> InputFileFormats { get; set; }
private void LoadOutputDevicePlugins(IEnumerable<IOutputDevicePlugin> outputDevicePlugins)
{
comboBoxOutputDevice.DisplayMember = "Name";
comboBoxOutputDevice.SelectedIndexChanged += new EventHandler(comboBoxOutputDevice_SelectedIndexChanged);
foreach (var outputDevicePlugin in outputDevicePlugins.OrderBy(p => p.Priority))
{
comboBoxOutputDevice.Items.Add(outputDevicePlugin);
}
comboBoxOutputDevice.SelectedIndex = 0;
}
void comboBoxOutputDevice_SelectedIndexChanged(object sender, EventArgs e)
{
panelOutputDeviceSettings.Controls.Clear();
Control settingsPanel;
if (SelectedOutputDevicePlugin.IsAvailable)
{
settingsPanel = SelectedOutputDevicePlugin.CreateSettingsPanel();
}
else
{
settingsPanel = new Label() { Text = "This output device is unavailable on your system", Dock=DockStyle.Fill };
}
panelOutputDeviceSettings.Controls.Add(settingsPanel);
}
private IOutputDevicePlugin SelectedOutputDevicePlugin
{
get { return (IOutputDevicePlugin)comboBoxOutputDevice.SelectedItem; }
}
// The rest of the code continues from here on...
}
}
Here is the Interface:
namespace NAudio.App
{
public interface IOutputDevicePlugin
{
IWavePlayer CreateDevice(int latency);
UserControl CreateSettingsPanel();
string Name { get; }
bool IsAvailable { get; }
int Priority { get; }
}
}
And just in case, here is one of the plugins:
DirectSoundOutPlugin.cs
namespace NAudio.App
{
[Export(typeof(IOutputDevicePlugin))]
class DirectSoundOutPlugin : IOutputDevicePlugin
{
private DirectSoundOutSettingsPanel settingsPanel;
private bool isAvailable;
public DirectSoundOutPlugin()
{
this.isAvailable = DirectSoundOut.Devices.Count() > 0;
}
public IWavePlayer CreateDevice(int latency)
{
return new DirectSoundOut(settingsPanel.SelectedDevice, latency);
}
public UserControl CreateSettingsPanel()
{
this.settingsPanel = new DirectSoundOutSettingsPanel();
return this.settingsPanel;
}
public string Name
{
get { return "DirectSound"; }
}
public bool IsAvailable
{
get { return isAvailable; }
}
public int Priority
{
get { return 3; }
}
}
}
Please help!
The error doesn't say it expects two arguments... it just says it doesn't take 0.
The constructor expects a single parameter - an IEnumerable<IOutputDevicePlugin>:
public AudioPlaybackPanel([ImportMany]IEnumerable<IOutputDevicePlugin> outputDevicePlugins)
{
...
}
You need to find something that implements the IOutputDevicePlugin interface and pass a collection of it, even if it's just an empty collection. (Passing null to the constructor will allow it to compile but will throw a runtime exception when you hit the loop in LoadOutputDevicePlugins.)
Considering the update to your question, something like this will get you up and running (although I doubt it means very much to pass an empty list):
var myPanel = new AudioPlaybackPanel(new List<DirectSoundOutPlugin>());
panel1.Controls.Add(myPanel);
It's worth asking whether you actually need to copy AudioPlaybackPanel.cs from the NAudio demo in its entirety. The reason it has this constructor is that it tries to demonstrate how you can use each and every one of NAudio's IWavePlayer implementations. But in a normal real-world application you would just select the one that was most appropriate for your use. e.g.
this.waveOut = new WaveOut();
waveOut.Init(new AudioFileReader("my file.mp3");
waveOut.Play();
So there's no need to incorporate the plug-in architecture from that particular demo, if all you want is just to play audio files.

MonoTouch Exception: Selector invoked from objective-c on a managed object of type that has been GC'ed

I'm getting this exception in my MonoTouch app and I can't seem to fix it. I've been trying for about 6 hours now and I'm having no luck.
My understanding of this exception is that an object is being referenced (or trying to be referenced) by MonoTouch but Garbage Collection has already disposed of it. Therefore it is looking to build a reference to it again using a constructor which I haven't set in the class, using a pointer.
Originally I thought that adding that constructor would be enough until I did some research and realised that at best, it would be a temporary bandage. What I'm finding strange is that as far as I can see I am holding a reference to everything. I've even gone a bit overboard and started creating member variables for things that don't necessarily need to be kept to try and capture the exception, but still nothing.
The error occurs when I load a ViewController, trigger a delegate which pushes a new ViewController onto the NavigationController (thus leaving the screen), hitting the back button and moving onto another view again. This consistently happens the same way and will always crash at this point, though not necessarily at the exact same time due to the way GC works I suppose.
The app is quite simple so far. The first screen loads 5 different charts using ShinobiCharts. Data Sources and Delegates are set in the ViewController and a ViewModel is sent to the View which then adds the charts to the subview. The exception occurs in ColumnChart (a custom made class inheriting from ShinobiChart). The exception will always be thrown on these controls never the View or the ViewController. However, which chart causes the exception is seemingly random every time.
How I'm storing the charts looks like this:
public class HomeViewModel
{
public ColumnChart MeetingsChart { get; set; }
public ColumnChart FirmDataChart { get; set; }
public ColumnChart SystemUseChart { get; set; }
public BarChart IllustrationsChart { get; set; }
public PieChart TermsOfBusinessChart { get; set; }
public ColumnChartDataSource MeetingsChartDataSource { get; set; }
public ColumnChartDataSource SystemUseChartDataSource { get; set; }
public StackChartDataSource FirmDataChartDataSource { get; set; }
public BarChartDataSource IllustrationsChartDataSource { get; set; }
public PieChartDataSource TermsOfBusinessDataSource { get; set; }
public BarChartDataProvider TermsOfBusinessDataProvider { get; set; }
public DashboardMeetingChartDelegate MeetingsChartDelegate { get; set; }
public ColumnChartDelegate SystemUseChartDelegate { get; set; }
public PieChartDelegate TermsOfBusinessDelegate { get; set; }
}
This class is then implemented in the Controller like this: (NOTE: Lots of code was stripped out as I didn't think it was needed)
public class HomeController : UIViewController
{
private HomeView _homeView;
private HomeViewModel _homeViewModel;
…..
public HomeController()
{
_homeViewModel = new HomeViewModel();
}
….
private void LoadCharts()
{
_homeViewModel.MeetingsChart = LoadMeetingsChart();
_homeViewModel.FirmDataChart = LoadFirmDataChart();
_homeViewModel.SystemUseChart = LoadSystemUseChart();
_homeViewModel.IllustrationsChart = LoadIllustrationsChart();
_homeViewModel.TermsOfBusinessChart = LoadTermsOfBusinessChart();
}
private ColumnChart LoadMeetingsChart()
{
ColumnChart meetingsChart = new ColumnChart(RectangleF.Empty);
meetingsChart.DataSource = _homeViewModel.MeetingsChartDataSource;
meetingsChart.Delegate = _homeViewModel.MeetingsChartDelegate;
meetingsChart.LicenseKey = LICENSE_KEY;
return meetingsChart;
}
}
Finally, the View:
public class HomeView : UIView
{
private HomeViewModel _homeViewModel;
public HomeView(RectangleF frame, UINavigationController navigationController, HomeViewModel homeViewModel)
: base(frame)
{
this._homeViewModel = homeViewModel;
this.BackgroundColor = UIColor.FromRGB(20, 20, 20);
this.Title = "Sales 360 Dashboard";
SetupChartBounds(UIApplication.SharedApplication.StatusBarOrientation);
}
public void SetupCharts()
{
SetupMeetingsChartHeaderBar();
SetupTermsOfBusinessChartHeaderBar();
SetupIllustrationsChartHeaderBar();
SetupSystemUserChartHeaderBar();
SetupFirmDataChartHeaderBar();
this.AddSubview(_homeViewModel.MeetingsChart);
this.AddSubview(_homeViewModel.TermsOfBusinessChart);
this.AddSubview(_homeViewModel.IllustrationsChart);
this.AddSubview(_homeViewModel.SystemUseChart);
this.AddSubview(_homeViewModel.FirmDataChart);
}
}
I'd really appreciate any help on this problem as I'm badly stuck on it. Thanks.
EDIT 1:
The HomeController is pushed from something called the SplashController. The only purpose of the SplashController is to display a splash screen view and call web services. The web services are called asynchronously and when completed will change the view to the home view where all charts are displayed.
Here is some of the SplashController class:
public class SplashController : UIViewController
{
private SplashView _splashView;
private SplashViewModel _splashViewModel;
private readonly string BACKGROUND_IMAGE_PATH;
private HomeController _homeController;
.....
public override void LoadView()
{
LoadViewModel();
_splashView = new SplashView(new RectangleF(0, 0, Dimensions.Width, Dimensions.Height), _splashViewModel);
this.View = _splashView;
}
// .... a number of service calls eventually leading to this
protected void GetTermsOfBusinessAllAgentsCompleted(object sender, GetTermsOfBusinessAllAgentsCompletedEventArgs e)
{
_servicesHelper.StopTimer(e.UserState as Timer);
if (e.Error != null)
HandleError(e.Error, "Terms of Business");
else
{
_termsOfBusinessGraphData = e.Result;
ChangeViewToHomeView();
}
}
private void ChangeViewToHomeView()
{
_homeController = new HomeController(_meetingsGraphData, _firmDataGraphData, _systemUseGraphData,
_illustrationsGraphData, _termsOfBusinessGraphData);
this.NavigationController.PushViewController(_homeController, false);
}
}
The HomeView is created in the LoadView method overridden in the HomeController class and basically looks like this:
_homeView = new HomeView(new RectangleF(0, 0, Dimensions.Width, Dimensions.Height), this.NavigationController, _homeViewModel);
this.View = _homeView;
As for the NavigationController, good question. This was something I was doing earlier where I was passing a NavigationController to a delegate. However, now all of this is done in the HomeController so it is no longer needed. Sorry about that, my mistake! I have now taken this out and tried re-running the app, the same thing still happens. Please ignore this bit.
EDIT 2:
Here is the delegate for one of the charts. I've missed out the methods which handles errors as it's not needed. The _meetingServices.GetAgentsData method calls a web service in that method which then returns to the event in this object:
public class DashboardMeetingChartDelegate : SChartDelegate
{
private UINavigationController _navigationController;
private CategoryGraph _meetingsGraphData;
private MeetingServices _meetingServices;
private ServicesHelper _servicesHelper;
private int _currentIndex;
public DashboardMeetingChartDelegate(UINavigationController navigationController, CategoryGraph meetingsGraphData)
{
this._navigationController = navigationController;
this._meetingsGraphData = meetingsGraphData;
_meetingServices = new MeetingServices();
_servicesHelper = new ServicesHelper();
_currentIndex = -1;
}
protected override void OnToggledSelection (ShinobiChart chart, SChartDataPoint dataPoint, SChartSeries series, PointF pixelPoint)
{
_currentIndex = dataPoint.Index;
_meetingServices.GetAgentsData(GetAgentsCompleted);
}
protected void GetAgentsCompleted(object sender, GetAgentsCompletedEventArgs e)
{
_servicesHelper.StopTimer(e.UserState as Timer);
if (e.Error != null)
HandleError(e.Error);
else
{
int rsmId = Convert.ToInt32(_meetingsGraphData.Data[0].SeriesDataPoints[_currentIndex].PointMetaData);
AgentContract currentAgent = e.Result.Agents.Where(agent => agent.Id == rsmId).First();
_navigationController.PushViewController(new MeetingController(currentAgent), true);
}
}
And here is the MeetingController - pretty similar to the other one except there's less to load:
public class MeetingController : UIViewController
{
private AgentContract _currentAgent;
private RsmChartView _meetingView;
private RsmChartViewModel _meetingViewModel;
private MeetingTypeController _meetingTypeController;
private ColumnChartDataProvider _columnChartDataProvider;
public MeetingController(AgentContract currentAgent)
{
_currentAgent = currentAgent;
}
public override void LoadView ()
{
LoadColumnChartDataProvider();
LoadViewModel();
InitialiseView();
}
private void LoadColumnChartDataProvider()
{
_columnChartDataProvider = new ColumnChartDataProvider();
_columnChartDataProvider.XValueList.Add(new NSString("Phone"));
_columnChartDataProvider.XValueList.Add(new NSString("Demo"));
_columnChartDataProvider.XValueList.Add(new NSString("Fact Finding"));
_columnChartDataProvider.YValueList.Add(61);
_columnChartDataProvider.YValueList.Add(22);
_columnChartDataProvider.YValueList.Add(27);
}
private void LoadViewModel()
{
_meetingViewModel = new RsmChartViewModel();
_meetingViewModel.Chart = LoadMeetingChart();
_meetingViewModel.RsmContentModel = LoadRsmContentModel();
}
private RsmContentModel LoadRsmContentModel()
{
RsmContentModel model = new RsmContentModel();
model.Name = _currentAgent.Name;
return model;
}
private void InitialiseView()
{
_meetingView = new RsmChartView(new RectangleF(0, 0, Dimensions.Width, Dimensions.Height), _meetingViewModel);
this.View = _meetingView;
}
private ColumnChart LoadMeetingChart()
{
ColumnChart meetingChart = new ColumnChart(new RectangleF(10, 220, Dimensions.Width - 20, 540));
_meetingTypeController = new MeetingTypeController();
_meetingViewModel.ChartDataSource = new ColumnChartDataSource(_columnChartDataProvider);
_meetingViewModel.ChartDelegate = new ColumnChartDelegate(this.NavigationController, _meetingTypeController);
meetingChart.DataSource = _meetingViewModel.ChartDataSource;
meetingChart.Delegate = _meetingViewModel.ChartDelegate;
return meetingChart;
}
public override void ViewDidLoad()
{
_meetingView.Load();
}
}
This condition is often hard to debug. Some changes have been made to MonoTouch to reduce such occurrences.
To try them I suggest you to try 6.0.7 (on the beta channel) and report any outstanding issue on the mailing list (or a bug report).
More details can be found in the release notes (direct link).

Datagrid not populating past headers

Thanks in advance for any help. I've been working with the tutorial listed here, but have run into an issue. I'm attempting to populate a datagrid in silverlight, but when I submit the button click, it will return the headers for columns but no data. I know data is in the system, so I'm confused why it's going to get the headers but not the actual data to populate. Code from my MainPage.xaml.cs and my data domain are below.
MainPage.xaml.cs
namespace SandCherryDemo
{
public partial class MainPage : UserControl
{
private SandCherryViewContext _sandCherryContext = new SandCherryViewContext();
public MainPage()
{
InitializeComponent();
}
private void StatusButton_Click(object sender, RoutedEventArgs e)
{
StatusButton.IsEnabled = false;
LoadOperation<SandCherryView> loadOp = this._sandCherryContext.Load(this._sandCherryContext.GetEQPByStatusQuery(StatusValue.Text), DataLoadedCallback, null);
SandCherryGrid.ItemsSource = loadOp.Entities;
}
void DataLoadedCallback(LoadOperation<SandCherryView> loadOperation)
{
StatusButton.IsEnabled = true;
}
}
}
SandCherryViewService.cs
[EnableClientAccess()]
public class SandCherryViewService : LinqToEntitiesDomainService<Charter_SandCherryEntities>
{
[Query(IsComposable=false)]
public IQueryable<SandCherryView> GetEQPByStatus(string status)
{
return this.ObjectContext.SandCherryViews.Where(e => e.StatusDescr.StartsWith(status) == true);
}
// TODO:
// Consider constraining the results of your query method. If you need additional input you can
// add parameters to this method or create additional query methods with different names.
// To support paging you will need to add ordering to the 'SandCherryViews' query.
public IQueryable<SandCherryView> GetSandCherryViews()
{
return this.ObjectContext.SandCherryViews;
}
public void InsertSandCherryView(SandCherryView sandCherryView)
{
if ((sandCherryView.EntityState != EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(sandCherryView, EntityState.Added);
}
else
{
this.ObjectContext.SandCherryViews.AddObject(sandCherryView);
}
}
public void UpdateSandCherryView(SandCherryView currentSandCherryView)
{
this.ObjectContext.SandCherryViews.AttachAsModified(currentSandCherryView, this.ChangeSet.GetOriginal(currentSandCherryView));
}
public void DeleteSandCherryView(SandCherryView sandCherryView)
{
if ((sandCherryView.EntityState != EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(sandCherryView, EntityState.Deleted);
}
else
{
this.ObjectContext.SandCherryViews.Attach(sandCherryView);
this.ObjectContext.SandCherryViews.DeleteObject(sandCherryView);
}
}
}
}
Since your data is not loaded yet. Try setting the ItemsSource of your grid in DataLoadedCallBack event -
void DataLoadedCallback(LoadOperation<SandCherryView> loadOperation)
{
StatusButton.IsEnabled = true;
SandCherryGrid.ItemsSource = loadOp.Entities;
}

Categories