I'm honestly not sure if databinding is the correct technique to achieve this, so if anyone could enlighten me I would be most grateful.
Basically what I'm trying to do is pass an object from the current page into a web user control as such (code simplified):
ExamplePage.aspx
<div>
<EC:AttachmentsView ID="AttachmentsView1" Attachments=<%# this.PageAttachments %> runat="server" />
</div>
ExamplePage.aspx.cs
public partial class ExamplePage : ProductBase
{
private LinkItemCollection _pageAttachments;
public LinkItemCollection PageAttachments
{
get { return _pageAttachments; }
}
public ExamplePage()
{
this.Load += new EventHandler(this.Page_Load);
}
protected void Page_Load(object sender, EventArgs e)
{
// Accessing and assigning attachments (EPiServer way)
_pageAttachments = CurrentPage["Attachments"] as LinkItemCollection ?? new LinkItemCollection();
}
}
The attachments view control has setters and getters for the Attachment and Label properties.
AttachmentsView.ascx.cs
namespace Example.Controls
{
[ParseChildren(false)]
public partial class AttachmentsView : EPiServer.UserControlBase
{
private string _label;
public string Label
{
get { return _label; }
set { _label = value; }
}
private LinkItemCollection _attachments;
public LinkItemCollection Attachments
{
get { return _attachments; }
set { _attachments = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (_attachments == null)
{
_attachments = CurrentPage["DefaultAttachments"] as LinkItemCollection ?? new LinkItemCollection();
}
}
}
}
I'm at the stage where I would expect the page attachments from the ExamplePage to be passed into the AttachmentsView control but the _attachments property is null.
Is what I'm trying to do possible? Is databinding the right technique, if so does anyone know material which explains the concepts easier than the horrible MSDN documentation?
I know I can probably achieve this by programmatically rendering the control but I'd like to try this method first.
Related
I am tying to further understand MVVM with some example scenario. I have a rootpage with a 'maindisplay' textblock. I would like to display 'status' or 'scenarios' from activation of any form of UI eg. togglebutton on the 'maindisplay' textblock.
I am able to bind the the page navigation info in the rootpageviewmodel to the textblock. However, I am not able to achieve the result when displaying info from different page.
I have checked another post multiple-viewmodels-in-same-view & Accessing a property in one ViewModel from another it's quite similar but it didn't work.
Please help. Thanks.
While accessing the RootPageViewModel should retain the instance?
View
<TextBlock Text="{x:Bind RootViewModel.MainStatusContent, Mode=OneWay}"/>
RootPage.xaml.cs
public sealed partial class RootPage : Page
{
private static RootPage instance;
public RootPageViewModel RootViewModel { get; set; }
public RootPage()
{
RootViewModel = new RootPageViewModel();
this.InitializeComponent();
// Always use the cached page
this.NavigationCacheMode = NavigationCacheMode.Required;
}
public static RootPage Instance
{
get
{
if (instance == null)
{
instance = new RootPage();
}
return instance;
}
}
private void nvTopLevelNav_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked)
{
contentFrame.Navigate(typeof(SettingsPage));
RootViewModel.MainStatusContent = "Settings_Page";
}
else
{
var navItemTag = args.InvokedItemContainer.Tag.ToString();
RootViewModel.MainStatusContent = navItemTag;
switch (navItemTag)
{
case "Home_Page":
contentFrame.Navigate(typeof(HomePage));
break;
case "Message_Page":
contentFrame.Navigate(typeof(MessagePage));
break;
}
}
}
}
RootPage ViewModel:
public class RootPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static RootPageViewModel instance = new RootPageViewModel();
public static RootPageViewModel Instance
{
get
{
if (instance == null)
instance = new RootPageViewModel();
return instance;
}
}
public RootPageViewModel()
{
}
private string _mainStatusContent;
public string MainStatusContent
{
get
{
return _mainStatusContent;
}
set
{
_mainStatusContent = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
MessagePage.xaml.cs - to access RootPage ViewModel
public sealed partial class MessagePage : Page
{
public MessagePageViewModel MessageViewModel { get; set; }
public MessagePage()
{
MessageViewModel = new MessagePageViewModel();
this.InitializeComponent();
// Always use the cached page
this.NavigationCacheMode = NavigationCacheMode.Required;
}
private void Message1_Checked(object sender, RoutedEventArgs e)
{
RootPageViewModel.Instance.MainStatusContent = "Message 1 Selected";
}
private void Message1_Unchecked(object sender, RoutedEventArgs e)
{
RootPageViewModel.Instance.MainStatusContent = "Message 1 De-Selected";
}
}
When I debug the value did write to the instance but did't update the TextBlock. Did I do anything wrong in my XAML binding?
UWP C# MVVM How To Access ViewModel from Other Page
The better way is make static variable for RootPage, but not make singleton instance for RootPage and RootPageViewModel.
For example:
public RootPage ()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
Instance = this;
RootViewModel = new RootPageViewModel();
}
public static RootPage Instance;
Usage
private void Message1_Checked(object sender, RoutedEventArgs e)
{
RootPage.Instance.RootViewModel.MainStatusContent = "Message 1 Selected";
}
private void Message1_Unchecked(object sender, RoutedEventArgs e)
{
RootPage.Instance.RootViewModel.MainStatusContent = "Message 1 De-Selected";
}
I am trying to pass strings between forms. Why does it not? Am I missing something or is it an error in the program or what?
On UserControl3
UserControl1 u1;
public UserControl3()
{
u1 = new UserControl1();
InitializeComponent();
}
On UserControl3
public void materialCheckBox1_CheckedChanged(object sender, EventArgs e)
{
if (materialCheckBox1.Checked)
{
u1.toUserControl3 = "GOINTHEBOX!";
}
else
{
u1.toUserControl3 = string.Empty;
}
}
On UserControl1
public string toUserControl3
{
get
{
return textBox1.Text;
}
set
{
textBox1.Text = value;
}
}
On UserControl1
public void textBox1_TextChanged(object sender, EventArgs e)
{
}
Changing the Text property on a control through a piece of code doesn't necessarily mean the value control will update. Typically you need some sort of binding between your property, in this case toUserControl3, and your control. You need a way to tell your control that value changed so it knows to update.
You could accomplish databinding in the following way:
Create a new class to handle state and binding: This eliminated any need to pass controls into constructors of other controls.
public class ViewModel : INotifyPropertyChanged
{
public string TextBoxText => CheckBoxChecked ? "GOINTOTHEBOX!" : string.Empty;
public bool CheckBoxChecked
{
get { return _checkBoxChecked; }
set
{
_checkBoxChecked = value;
OnPropertyChanged("CheckBoxChecked");
}
}
private bool _checkBoxChecked;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
This is your main form
public void Form1
{
public Form1(ViewModel viewModel)
{
UserControl1.DataBindings.Add("TextBoxTextProperty", viewModel, "TextBoxText");
UserControl3.DataBindings.Add("MaterialCheckBoxCheckedProperty", viewModel, "CheckBoxChecked");
}
}
UserControl1
public void UserControl1()
{
public string TextBoxTextProperty
{
get { return textBox1.Text; }
set { textBox1.Text = value; }
}
}
UserControl3
public void UserControl3()
{
public bool MaterialCheckBoxCheckedProperty
{
get { return materialCheckBox1.Checked; }
set { materialCheckBox1.Checked = value; }
}
}
I didn't know how better to word the title so I went with solution that came to my mind.
Here is the problem. I have a page that has list and each item on the lists opens a detail page (on click). But the VM is reused, which causes me several problems.
Previous data can be seen for split second when opening a the detail page
I need certain properties to be set to specific values when the page open, but since the VM is reused it keeps all the values from the previous detail and this messes up my logic.
This UWP app. I'm using Template10 framework's NavigationService to move between pages.
Main Page ViewModel
public class MainPageViewModel : ViewModelBase {
private List<MangaItem> _mangaList;
public List<MangaItem> mangaList {
get { return _mangaList; }
set { Set(ref _mangaList, value); }
}
private string _mainSearchText;
public string mainSearchText {
get { return _mainSearchText; }
set { Set(ref _mainSearchText, value); }
}
public MainPageViewModel() {
_mangaList = new List<MangaItem>();
mangaList = new List<MangaItem>();
Initialize();
}
private async void Initialize() {
mangaList = await MangaListGet.GetListAsync();
}
public async void MainSearchSubmitted() {
mangaList = await MangaListGet.GetListAsync(_mainSearchText);
}
public void MangaSelected(object sender, ItemClickEventArgs e) {
var mangaItem = (MangaItem)e.ClickedItem;
NavigationService.Navigate(typeof(Views.MangaDetail), mangaItem.id);
}
}
And Detail Page ViewModel
class MangaDetailViewModel : ViewModelBase {
private MangaItem _mangaDetail;
public MangaItem mangaDetail {
get { return _mangaDetail; }
set { Set(ref _mangaDetail, value); }
}
private string _mangaId;
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> suspensionState) {
_mangaId = parameter as string;
Initialize();
await Task.CompletedTask;
}
private async void Initialize() {
mangaDetail = await MangaDetailGet.GetAsync(_mangaId);
}
public void ChapterSelected(object sender, ItemClickEventArgs e) {
var _chapterId = (ChapterListItem)e.ClickedItem;
NavigationService.Navigate(typeof(Views.ChapterPage), _chapterId.id);
}
}
This code only shows the first problem is displaying previously loaded data for a split second. If needed I will add code that showcases the other problem, but I' not sure if it's really relevant right now. I'm thinking that maybe my entire logic is flawed or something.
EDIT:
<Page.DataContext>
<vm:ChapterPageViewModel x:Name="ViewModel" />
</Page.DataContext>
where vm is xmlns:vm="using:MangaReader.ViewModels".
Another solution is to use Bootstrapper.ResolveforPage() which is intended to handle dependency injection but would easily serve your needs. Like this:
[Bindable]
sealed partial class App : BootStrapper
{
static ViewModels.DetailPageViewModel _reusedDetailPageViewModel;
public override INavigable ResolveForPage(Page page, NavigationService navigationService)
{
if (page.GetType() == typeof(Views.DetailPage))
{
if (_reusedDetailPageViewModel == null)
{
_reusedDetailPageViewModel = new ViewModels.DetailPageViewModel();
}
return _reusedDetailPageViewModel;
}
else
{
return null;
}
}
}
The NavigationService will treat this the same as any other view-model. Meaning it will call OnNavTo() and the other navigation overrides you include.
Best of luck.
While Template10 documentation states the NavigationCacheMode is disabled by default, that isn't the case in it's example templates (as of writing this). This is set in View C# code (.xaml.cs file).
.xaml.cs file
namespace MangaReader.Views {
public sealed partial class MangaDetail : Page {
public MangaDetail() {
InitializeComponent();
//NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Enabled; //this was set by default
NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Disabled;
}
}
}
Now, new ViewModel will be created each time you access a this page.
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
I am implementing MVP pattern in my application.
But I am getting NullReferenceException on the Page_Load of my view class.
Here is my presenter class:
using Microsoft.Practices.CompositeWeb;
namespace PresenterDLL
{
public class NamePresenter : Presenter<IProduct>
{
public void SayHello()
{
View.Name = 200;
}
}
public interface IProduct
{
int Name { get; set; }
}
}
and here is code behind class of my view:
using System;
using PresenterDLL;
using Microsoft.Practices.ObjectBuilder;
public partial class _Default : BasePage, IProduct
{
private NamePresenter _presenter;
[CreateNew]
public NamePresenter Presenter
{
set
{
this._presenter = value;
_presenter.View = this;
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
this._presenter.OnViewInitialized();
this._presenter.SayHello();
}
this._presenter.OnViewLoaded();
}
public int Name
{
get
{
return 10;
}
set
{
TextBox1.Text = value.ToString();
}
}
}
while running the application I am getting NullreferenceException in the Page_Load method,
as _presenter is null. Because it's never called. So, what should i do so that ObjectBuilder can call it before the page life cycle begins..
My base page class is:
public class BasePage : Microsoft.Practices.CompositeWeb.Web.UI.Page
{
public BasePage()
: base()
{
// _ctlForm = this;
// WebClientApplication.BuildItemWithCurrentContext(this);
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
//Disable all caching
Response.CacheControl = "no-cache";
Response.AddHeader("Pragma", "no-cache");
Response.Expires = -1;
}
protected override void OnPreInit(EventArgs e)
{
//This has been moved to the top since the preinit in the base clase is responsible
//for dependency injections.
//ObjectFactory.BuildUp(this);
base.OnPreInit(e);
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
}
}
Can someone please figure out where is the problem...
I think you may have one of two following problems: setter of the Presenter property is not called at all or it is called but null is assigned. I think you should try to put a break point in the setter of Presenter property to see what is happening.
You can try overriding PreInit (http://dotnetchris.wordpress.com/2009/02/16/creating-a-generic-model-view-presenter-framework/):
protected override void OnPreInit(EventArgs e)
{
ObjectFactory.BuildUp(this);
base.OnPreInit(e);
}