Binding in User Controls - c#

let's say I've got this code (in Winforms):
public class SomeClass
{
public string Name { get; set; }
}
public partial class SomeControl : UserControl
{
private SomeClass inClass;
public string MyName { get; set; }
public SomeControl(SomeClass someClass)
{
InitializeComponent();
this.inClass = someClass;
SetupBinding();
}
private void SetupBinding()
{
this.DataBindings.Clear();
this.DataBindings.Add("MyName", this.inClass, "Name");
}
}
If I'll change the the value of SomeClass.Name outside the user control, the property MyName never changes. What am I doing wrong?
Thank you

SomeClass must impelement INotifyPropertyChanged or have event named NameChanged if you want bidirectional data binding. You can implement it yourself, but I highly recommend Fody.PropertyChanged project.
PS: I wrote some extension method to create binding more easier and refactoring friendly way. In your case it would look like this:
this.BindTo(inClass, c => c.MyName , m => m.Name);
The method itself:
public static class BindingExtensions
{
public static Binding BindTo<TControl, TControlProperty, TModel, TModelProperty>(this TControl control, TModel model, Expression<Func<TControl, TControlProperty>> controlProperty, Expression<Func<TModel, TModelProperty>> modelProperty, string format = null)
where TControl : Control
{
var controlPropertyName = ((MemberExpression)controlProperty.Body).Member.Name;
var modelPropertyName = ((MemberExpression)modelProperty.Body).Member.Name;
var formattingEnabled = !string.IsNullOrWhiteSpace(format);
var binding = control.DataBindings.Add(controlPropertyName, model, modelPropertyName, formattingEnabled, DataSourceUpdateMode.OnPropertyChanged);
if (formattingEnabled)
{
binding.FormatString = format;
}
return binding;
}
}

Related

How to change value in one class if value changes in another

Let's say I have a parameter in my ViewModel:
public string ChosenQualityParameter
{
get => DefectModel.SelectedQualDefectParameters?.Name ?? "Не выбран параметр";
}
and I have a class DefectModel with parameter SelectedQualDefectParameters.Name in it. I want to change the UI binded to ChosenQualityParameter, when theName parameter changes too.
But I don't know how to do this properly. Any suggestions? Thanks in advance.
You might define your ViewModel class like this:
public class ViewModel
{
private DefectModel _defectModel;
public ViewModel(DefectModel defectModel)
{
_defectModel = defectModel;
}
public string ChosenQualityParameter
{
get => _defectModel.SelectedQualDefectParameters?.Name ?? "Не выбран параметр";
}
}
I personally do not like such dependencies in viewmodels, but it might get the job done here. It seems to work in a console application anyway:
using System;
public class Parameters
{
public string Name { get; set; }
}
public class DefectModel
{
public Parameters SelectedQualDefectParameters { get; set; }
}
public class ViewModel
{
private DefectModel _defectModel;
public ViewModel(DefectModel defectModel)
{
_defectModel = defectModel;
}
public string ChosenQualityParameter
{
get => _defectModel.SelectedQualDefectParameters?.Name ?? "Не выбран параметр";
}
}
class Program
{
static void Main()
{
var defectModel = new DefectModel
{
SelectedQualDefectParameters = new Parameters
{
Name = "test"
}
};
var viewModel = new ViewModel(defectModel);
Console.WriteLine(viewModel.ChosenQualityParameter);
defectModel.SelectedQualDefectParameters.Name = "changed";
Console.WriteLine(viewModel.ChosenQualityParameter);
Console.ReadKey();
}
}
Thanks to #Knoop and #BartHofland, I've solved my issue by using INotifyPropertyChanged in my DefectModel and SelectedQualDefectParameters classes.
For setting ChosenQualityParameter I used MessagingCenter to send new value.

Get DataContract Name property

I have a class that I have applied a DataContract attribute with a name property.
[DataContract(Name ="ProductInformation")]
public class ProductViewModel : BaseViewModel
{
[DataMember]
public string ProductName {get; set;}
}
All of my other ViewModel classes also inheriting from the BaseViewModel class.How do I retrieve the Name property from the BaseViewModel.
Update
If i understand you correctly, you want the most derived attribute when called form the base class. Note typeof(T) gets the instantiated type, I.e the most derived type
Also note GetTypeInfo() is basically added because this is a Xamarin Question
public static List<DataContractAttribute> GetDataContracts<T>()where T : class
{
return typeof(T).GetTypeInfo()
.GetCustomAttributes(false)
.OfType<DataContractAttribute>()
.ToList();
}
Original
public static List<DataContractAttribute> GetDataContracts<T>()where T : class
{
return typeof(T).BaseType?
.GetCustomAttributes(false)
.OfType<DataContractAttribute>()
.ToList();
}
public static void Main()
{
var attribute = GetDataContracts<ProductViewModel>().FirstOrDefault();
Console.WriteLine(attribute?.Name ?? "NoCigar");
}
PCL BaseViewModel Code
public class BaseViewModel
{
public static Dictionary<Assembly, Type> AllAssembelyUsedInBaseViewModel = new Dictionary<Assembly, Type>();
public static void RegisterAssemblyAndBase(Assembly assembly, Type baseType)
{
AllAssembelyUsedInBaseViewModel.Add(assembly, baseType);
}
static BaseViewModel()
{
RegisterAssemblyAndBase(typeof(BaseViewModel).GetTypeInfo().Assembly, typeof(BaseViewModel));
}
public static void GetDataContractNameFromAllAssembly()
{
List<string> dname = new List<string>();
foreach (var item in BaseViewModel.AllAssembelyUsedInBaseViewModel)
{
var assembly = item.Key;
var types = assembly.DefinedTypes.Where(x => x.BaseType == item.Value);
foreach (var type in types)
{
var attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
var dt = attributes.FirstOrDefault() as DataContractAttribute;
if (dt != null)
{
dname.Add(dt.Name);
}
}
}
}
}
Register other UI Assembelies
public MainWindow()
{
InitializeComponent();
BaseViewModel.RegisterAssemblyAndBase(typeof(MainWindow).Assembly, typeof(BaseViewModel));
}
Get All Datacontract Name by calling
BaseViewModel.GetDataContractNameFromAllAssembly();

Passing Parameter From Main to Detail in MVVMCross

I am trying to pass the selected item from the list to the detail view, but myitem is null in the DetailViewmodel even though it is not in the MyViewModel.
MyViewModel.cs
public virtual ICommand ItemSelected
{
get
{
return new MvxCommand<MyViewModel>(item =>{SelectedItem = item;});
}
}
public MyViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
// myItem is NOT null here!!!
ShowViewModel<MyDetailViewModel>(new { date = Date, myItem = _selectedItem });
RaisePropertyChanged(() => SelectedItem);
}
}
MyDetailViewModel.cs
public class MyDetailViewModel: MvxViewModel
{
private MyViewModel _myItem;
public void Init(DateTime date, MyViewModel myItem = null)
{
// myItem is NULL here!!!
_myItem = myItem;
}
}
You can use a parameter object, because you can only pass one parameter. I usually crate a nested class Parameter for this.
public class MyDetailViewModel: MvxViewModel
{
private MyViewModel _myItem;
public class Parameter
{
public DateTime Date {get; set; }
public string Name {get; set;}
}
public void Init(Parameter param)
{
Name = param.Name;
}
}
and show the viewmodel like:
ShowViewModel<MyDetailViewModel>(new MyDetailViewModel.Parameter { Date = Date, Name = _selectedItem.Name });
But be aware!
The paramters cannot be complex due certain platform issues. You might have to pass only the Id of your Item within the Parameter object and then load MyItem in your Init function. Or you pass only a string and use serialization: https://stackoverflow.com/a/19059938/1489968
myItem is null because if you pass typed parameter to Init it should be the only parameter you pass. According to MvvmCross ViewModel Creation documentation:
Init() can come in several flavors:.
individual simply-Typed parameters
a single Typed parameter object with simply-Typed properties
as InitFromBundle() with an IMvxBundle parameter - this last flavor is always supported via the IMvxViewModel interface.

MVC: Custom, fluent Html Helpers

I'm writing a custom HTML helper to display a Grid and I'm focussing myself on Telerik and other parties for the syntax I would like to use.
I have a model with 3 properties (Name, DateUpdated and DateCreted) and an IEnumerable of that one is passed to my view:
#model IEnumerable<GridPageFolderViewModel>
Then I have my static HtmlHelperExtensions class:
public static class HtmlHelperExtensions
{
#region Grid
public static GridBuilder<TModelEntry> GridFor<TModel, TModelEntry>(this HtmlHelper<TModel> htmlHelper, TModelEntry model)
{
return new GridBuilder<TModelEntry>();
}
#endregion
}
This class does return a GridBuilder which looks as the following:
public class GridBuilder<TModel> : IGridBuilder
{
#region Properties
private string name { get; set; }
#endregion
#region Methods
public GridBuilder<TModel> WithColumns(Action<ColumnBuilder<TModel>> function)
{
return this;
}
internal MvcHtmlString Render()
{
return new MvcHtmlString("This is a value.");
}
#endregion
#region IGridBuilder Members
public GridBuilder<TModel> Name(string name)
{
this.name = name;
return this;
}
#endregion
#region IHtmlString Members
public string ToHtmlString()
{ return Render().ToHtmlString(); }
#endregion
}
And then I have my ColumnBuilder class.
public class ColumnBuilder<TModel>
{
public void Bind<TItem>(Func<TModel, TItem> func)
{
}
}
With all this code into place (nothing is rendered at this point), I can use the following syntax:
#(Html.GridFor(new GridPageFolderViewModel())
.Name("PageOverviewGrid")
.WithColumns(column =>
{
column.Bind(c => c.Name);
column.Bind(c => c.DateCreated);
column.Bind(c => c.DateUpdated);
}
)
The 'problem' here is that I need to specify the type of object that a single item in the Grid is holding (the GridPageFolderViewModel), otherwise, I cannot access the properties in the column binder code.
Anyone has some advice on how I can get rid of it?
As the view model is IEnumerable<GridPageFolderViewModel>, you just need to declare your helper like this:
public static GridBuilder<TModelEntry> GridFor<TModelEntry>(this HtmlHelper<IEnumerable<TModelEntry>> htmlHelper)
{
return new GridBuilder<TModelEntry>();
}
It would mean your helper can only be used on views where the model is an IEnumerable<T>, but I think that makes sense.
Now in your view you can do:
#(Html.GridFor()
.Name("PageOverviewGrid")
.WithColumns(column =>
{
column.Bind(c => c.Name);
column.Bind(c => c.DateCreated);
column.Bind(c => c.DateUpdated);
}
)
PS. Don't worry if you need to access the model inside your helper. You can still retrieve it from htmlHelper.ViewData.Model, which will be nicely typed as IEnumerable<TModelEntry>
Hope it helps!

DataGridView List<T> column?

I have a datagridView, that is bound to a List. This List is made up of my class which contains 2 public properties, a String Name, and another List CustomList. See below:
public class MyClass2
{
public string Name
{ get; set;}
public string Description
{
get;
set;
}
}
public class MyClass
{
List<MyClass2> myList;
public string Name
{
get;
set;
}
public List<MyClass2> CustomList
{
get { return myList ?? (myList= new List<MyClass2>()); }
}
}
And then in my designer page:
List<MyClass> myClassList = new List<MyClass>();
dataGridView.DataSource = myClassList;
As it is right now, the only column that appears in the grid, is the MyClass:Name column, and the CustomList column does not show up. What I'd like is the CustomList column to show and to display something like "Collection" with the "..." button showing, and when it is clicked to have the "Collection Editor" to popup.
Does anyone know if this is possible and how to enable it? If there's a tutorial or anything that would help me out I'd appreciate that too. Thanks.
Using generics, I think, is a clean solution:
public class Sorter<T>: IComparer<T>
{
public string Propiedad { get; set; }
public Sorter(string propiedad)
{
this.Propiedad = propiedad;
}
public int Compare(T x, T y)
{
PropertyInfo property = x.GetType().GetProperty(this.Propiedad);
if (property == null)
throw new ApplicationException("El objeto no tiene la propiedad " + this.Propiedad);
return Comparer.DefaultInvariant.Compare(property.GetValue(x, null), property.GetValue(y, null));
}
}
Usage example:
string orderBy = "propertyName";
bool orderAsc = true;
List<MyExampleClass> myClassList = someMethod();
if (!string.IsNullOrEmpty(orderBy))
{
myClassList.Sort(new Sorter<MyExampleClass>(orderBy));
if (!orderAsc) myClassList.Reverse();
}
Short answer: Yes, you can do it with some code.
Long answer: To write the code is gonna be a pain in the ass, as you would have to know not only how the DataGridView behaves with custom columns, but you would need to know how to expose design time elements at runtime, which requires quite a bit of plumbing. Extensive knowledge about the PropertyGrid must also be known.
Note: This might a fun component to write. (I might actually tackle it if I get some time)
So using the 'button' approach posted by Dave, and some code that I found that implements the CollectionEditor, I can edit the CustomList in MyClass2
Here's my solution, although not quite as clean as I'd like:
Put this class somewhere:
class MyHelper : IWindowsFormsEditorService, IServiceProvider, ITypeDescriptorContext
{
public static void EditValue(IWin32Window owner, object component, string propertyName)
{
PropertyDescriptor prop = TypeDescriptor.GetProperties(component)[propertyName];
if (prop == null) throw new ArgumentException("propertyName");
UITypeEditor editor = (UITypeEditor)prop.GetEditor(typeof(UITypeEditor));
MyHelper ctx = new MyHelper(owner, component, prop);
if (editor != null && editor.GetEditStyle(ctx) == UITypeEditorEditStyle.Modal)
{
object value = prop.GetValue(component);
value = editor.EditValue(ctx, ctx, value);
if (!prop.IsReadOnly)
{
prop.SetValue(component, value);
}
}
}
private readonly IWin32Window owner;
private readonly object component;
private readonly PropertyDescriptor property;
private MyHelper(IWin32Window owner, object component, PropertyDescriptor property)
{
this.owner = owner;
this.component = component;
this.property = property;
}
#region IWindowsFormsEditorService Members
public void CloseDropDown()
{
throw new NotImplementedException();
}
public void DropDownControl(System.Windows.Forms.Control control)
{
throw new NotImplementedException();
}
public System.Windows.Forms.DialogResult ShowDialog(System.Windows.Forms.Form dialog)
{
return dialog.ShowDialog(owner);
}
#endregion
#region IServiceProvider Members
public object GetService(Type serviceType)
{
return serviceType == typeof(IWindowsFormsEditorService) ? this : null;
}
#endregion
#region ITypeDescriptorContext Members
IContainer ITypeDescriptorContext.Container
{
get { return null; }
}
object ITypeDescriptorContext.Instance
{
get { return component; }
}
void ITypeDescriptorContext.OnComponentChanged()
{ }
bool ITypeDescriptorContext.OnComponentChanging()
{
return true;
}
PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor
{
get { return property; }
}
#endregion
Add a button column to the data grid:
DataGridViewButtonColumn butt = new DataGridViewButtonColumn();
butt.HeaderText = "CustomList";
butt.Name = "CustomList";
butt.Text = "Edit CustomList...";
butt.UseColumnTextForButtonValue = true;
dataGridView.Columns.Add(butt);
dataGridView.CellClick += new DataGridViewCellEventHandler(dataGridView_CellClick);
Then call it in the button handler of the cell click.
if (e.RowIndex < 0 || e.ColumnIndex != dataGridView.Columns["CustomList"].Index)
return;
//get the name of this column
string name = (string)dataGridView[dataGridView.Columns["Name"].Index, e.RowIndex].Value;
var myClassObject= myClassList.Find(o => o.Name == name);
MyHelper.EditValue(this, myClassObject, "CustomList");
I'd still be interested in hearing other approaches, and not having to implement my own CollectionEditor. And I'm still interested in having it look more like what the TabControl uses to add TabPages in the PropertyGrid...by displaying the "..." button...but this might work for now.
What you want to do is add a column template with a button in it:
http://geekswithblogs.net/carmelhl/archive/2008/11/11/126942.aspx
In the handler for the button, get the selected MyClass item from the collection and bind its list property to a grid in your popup.

Categories

Resources