xamarin: binding object and object parent to property change - xamarin

I have a model "optionModel" with "data" member as a list of "options". Option has title and status members:
class optionModel
{
...
public List<option> data {get; private set;}
}
class option
{
...
public String title{get;set}
public bool status {get;set}
}
My view for the model is a grid, which is populating like this:
optionModel _m = new optionModel();
Grid g = new Grid();
foreach (option op in _m.data)
{
..
TextSwitch tc = new TextSwitch(); // view class
tc.BindingContext = op;
tc.SetBinding(TextSwitch.IsToggledProperty, "status");
g.Children.Add(tc, 0, iRow);
..
}
Everything works as expected. "status" property of the "option" is updated, I am happy.
My question is: In addition to "option" I also want "optionModel" to be notified when any of the "option" change their status. One immediate solution is
to construct the "option" to have pointer to the optionModel and notify optionModel it within "status" set method of "option".
But perhaps there is a way to do it via Xamarin binding mechanism without introducing optionModel as a member of "option"?
Thanks!

Related

Passing a single object from a controller to a view

I have a web-based game built using .netCore 2 and entity framework.
The game works, but I just added a new feature. In one of the views, the user can press a button that creates a new character via a controller.
Here is that controller method:
public async Task<IActionResult> AddCharacter(Guid playerId)
{
ICharManagement charMgr = new CharManagementClient();
Character newCharacter = await charMgr.AddCharacterAsync(
"Test Character",
new Guid("1e957dca-3fe1-4214-b251-a96e0106997a")
);
var newCharacterObject = newCharacter;
return View(newCharacterObject);
}
The Character object is from an API I use and it looks like this:
public partial class Character : object
{
private System.Guid Id;
private string Name;
private System.Nullable<System.DateTime> DateCreated;
private int32 CharPlayer;
private bool Playable;
}
Stepping through the controller in Visual Studio, I can see that a new Character object is returned and the newCharacterObject looks like this:
Id "1e957dca-3fe1-4214-b251-a96e0106997a"
Name "Test Character"
DateCreated "2/12/2019"
CharPlayer 3821
Playable "true"
In my view (cshtml), I have the model set to this:
#model IEnumerable<Character>
However, when I run it, I get this error:
InvalidOperationException: The model item passed into the
ViewDataDictionary is of type 'Character', but this
ViewDataDictionary instance requires a model item of type
'System.Collections.Generic.IEnumerable`1[Character]'.
So how do I get my view to display a single Character object(newCharacterObject) or how would I convert "newCharacterObject" into what the view needs?
Thanks!
Edit: Formatting
If you only need one character model, you can just use #model Character at the top of your cshtml
IEnumerable is used for iterating through a collection of objects.

Expression.Property(param, field) is "trolling" me [System.ArgumentException] = {"Instance property 'B.Name' is not defined for type A"}

Once again, I am facing an issue, this time with LINQ Expression builder and this time I am even struggling to find the reason why it's not working. I have a Database-First EF project with quite a few tables. For this specific case, I have to use 2 of them - DocHead and Contragent. MyService.metadata.cs looks like this:
[MetadataTypeAttribute(typeof(DocHead.DocHeadMetadata))]
public partial class DocHead
{
// This class allows you to attach custom attributes to properties
// of the DocHead class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class DocHeadMetadata
{
// Metadata classes are not meant to be instantiated.
private DocHeadMetadata()
{
}
public string doc_Code { get; set; }
public string doc_Name { get; set; }
public string doc_ContrCode { get; set; }
//...
[Include]
public Contragent Contragent { get; set; }
}
}
[MetadataTypeAttribute(typeof(Contragent.ContragentMetadata))]
public partial class Contragent
{
// This class allows you to attach custom attributes to properties
// of the Contragent class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class ContragentMetadata
{
// Metadata classes are not meant to be instantiated.
private ContragentMetadata()
{
}
public string Code { get; set; }
public string Name { get; set; }
//...
I take some docHeads like this:
IQueryable<DocHead> docHeads = new MyEntities().DocHead;
Then I try to sort them like this:
docHeads = docHeads.OrderByDescending(x => x.Contragent.Name);
It is all working like I want it. I get those docHeads sorted by the name of the joined Contragent. My problem is that I will have to sort them by a field, given as a string parameter. I need to be able to write something like this:
string field = "Contragent.Name";
string linq = "docHeads = docHeads.OrderByDescending(x => x." + field + ")";
IQueryable<DocHead> result = TheBestLinqLibraryInTheWorld.PrepareLinqQueryable(linq);
Unfortunately, TheBestLinqLibraryInTheWorld does not exist (for now). So, I have set up a method as a workaround.
public static IQueryable<T> OrderByField<T>(this IQueryable<T> q, string SortField, bool Ascending)
{
var param = Expression.Parameter(typeof(T), "x");
var prop = Expression.Property(param, SortField); // normally returns x.sortField
var exp = Expression.Lambda(prop, param); // normally returns x => x.sortField
string method = Ascending ? "OrderBy" : "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp); // normally returns sth similar to q.OrderBy(x => x.sortField)
return q.Provider.CreateQuery<T>(mce);
}
Normally... yes, when it comes to own properties of the class DocHead - those prefixed with doc_. The disaster strikes when I call this method like this:
docHeads = docHeads.OrderByField<DocHead>("Contragent.Name", true); // true - let it be Ascending order
To be more specific, the exception in the title is thrown on line 2 of the method OrderByField():
var prop = Expression.Property(param, SortField);
In My.edmx (the model), the tables DocHead and Contragent have got a relation already set up for me, which is the following: 0..1 to *.
Once again, I have no problem writing "static" queries at all. I have no problem creating "dynamic" ones via the method OrderByField(), but only when it comes to properties of the class DocHead. When I try to order by a prop of the joined Contragent class - the disaster strikes. Any help will be greatly appretiated, thank you!
The problem is that Expression.Property method does not support nested properties. It does exactly what it says - creates expression that represents a property denoted by propertyName parameter of the object denoted by the expression parameter.
Luckily it can easily be extended. You can use the following simple Split / Aggregate trick anytime you need to create a nested property access expression:
var prop = SortField.Split('.').Aggregate((Expression)param, Expression.Property);

Validation for items in ObservableCollection bound to DataGrid when validation of one item of collection depends on other items

I am using MVVM and displaying some items on a DataGrid. My model is RecordingInfo and looks like:
public class RecordingInfo : IDataErrorInfo
{
public RecordingInfo(string fullDirectoryName, string recordingName, int recordingNumber)
{
FullDirectoryName = fullDirectoryName;
RecordingName = recordingName;
RecordingNumber = recordingNumber;
}
public string FullDirectoryName { get; internal set; }
public string RecordingName { get; set; }
public int RecordingNumber { get; internal set; }
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string propertyName]
{
get {
if (propertyName == "RecordingName")
{
if (this.RecordingName.Length < 2)
return "Recording Name must be at least two characters";
}
return null;
}
}
}
I end up with a collection of these RecordingInfo programmatically. The user is not allowed to do much with these but he/she can change the RecordingName subject to the name being 2 characters or more AND that the RecordingName must be unique. I.e. no changing it to match another RecordingName. I have taken care of the first requirement. It's the second one that is giving me grief.
For my ViewModel, I have
public class RecordingListViewModel : ViewModelBase//, IDataErrorInfo
{
private ObservableCollection<RecordingInfo> _recordings = null;
public RecordingListViewModel()
{
}
public ObservableCollection<RecordingInfo> Recordings
{
get
{
return _recordings;
}
}
// more stuff left off for brevity
In my view I bind the collection to a DataGrid and have:
<DataGrid ItemsSource="{Binding Path=Recordings}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Recording" IsReadOnly="False" EditingElementStyle="{StaticResource CellEditStyle}" ElementStyle="{StaticResource CellNonEditStyle}" Binding="{Binding RecordingName, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" >
</DataGridTextColumn>
...
</DataGrid.Columns>
My way of checking for 2 or more characters works great. But this doesn't work for checking that the user is not trying to give a recording an existing name. Presumably, I need to somehow handle this at the ViewModel layer since the ViewModel knows about all Recordings. I tried playing with having my ViewModel derive from IDataErrorInfo but the property indexer never gets called, which makes sense as it's the Observable collection and therefore the individual RecordingInfos that are bound. I also thought about doing something with a "Lost Focus" event, but DataGridTextColumn doesn't seem to have that. I would think this is a somewhat common problem: validation must take into account relationships between the items of the collection.
By the way, I'm not wedded to the IDataErrorInfo and I am not opposed to other changes in architecture. Please let me know if I can provide more details. I have tried to provide a minimal amount of code. Clearly, this is part of a much bigger project. Any advice is appreciated.
Thanks,
Dave
I would do the following
1) Make RecordingInfo implement INotifyPropertyChanged
2) Use a BindingList<> instead of ObservableCollection<>
In your viewmodel, subscribe to the BindingList.ListChanged Event. This event will fire when items are added and removed, but also when the top level properties on RecordingInfo changes. In the case of a property being changed, the ListChangedEventArgs.PropertyDescriptor property will contain the name of the property, if you want to run validation for just that property (be careful though, this can be null when the item as added/removed). You'll need to use the ListChangedType property to determine the reason of the event (E.x.: Reset means everything changed, ItemAdded means the item was added, but the ItemChanged means a property changed as occurred on an existing item.
You can have the parent ViewModel (that contains and creates your RecordingInfos) pass a name validation Func in their constructors for them to call when validating their name changes.

ASP.NET MVC 3 nested lists

I'm new to the MVC world and need some guidance. I'm trying to create a vertical multi level menu for a website. The data for the menu is stored in a database table with the following structure:
ID, categoryName, parentID
How do I go about this with regular ADO.NET and MVC? As far as I can tell I need to make a MenuItem class with int ID, string name and List<MenuItem> Children properties. I then need to make and instance of this class representing the vertical menu and then I need to genereate the HTML for the view.
I have searched the net for an easy to follow example but I'm not finding anything I can understand. If there is anyone out there that can guide me through this it would be very much apprediated!
You should edit your question or split it up into more concrete questions.
I will answer the concrete question in your comment on how to load your MenuItems recursively.
You wrote you use regular ADO.NET. I was free to use dapper in my sample code which makes life a bit easier:
So here is you MenuItem class:
public class MenuItem
{
public int Id { get; set; }
public int? ParentId { get; set; }
public List<MenuItem> Children { get; set; }
}
Now we open a connection and load the whole MenuItems table into a single collection:
SqlConnection conn = new SqlConnection("...");
conn.Open();
IEnumerable<MenuItem> allItems = conn.Query<MenuItem>("SELECT * FROM MenuItems");
I pretend you will have a single MenuItem as the root item with ParentId = null. Lets find it:
MenuItem rootMenu = allItems.Single(m => m.ParentId == null);
The last thing we have to do is to rearrange the items in a hierachical tree. We do this with a function which works recursively:
loadChildren(rootMenu, allItems);
Here is the function. It simply looks up the children of the passed item and calls itself on all found childnodes:
private static void loadChildren(MenuItem currentItem,
IEnumerable<MenuItem> allItems)
{
currentItem.Children = allItems
.Where(m => m.ParentId == currentItem.Id).ToList();
foreach (var childItem in currentItem.Children)
{
loadChildren(childItem, allItems);
}
}

ModelBinding enum values for selectlist and selected item

Ok, this was supposed to be a question asking for help getting my form to work properly. In the process of creating an example to post, I figured out what the fix is.
So now it's a become a question about why it works one way and not the other. I really cannot understand the behaviour. Have I found a bug in MVC? Or is there something I don't understand about html requests which makes this behaviour correct?
The example invloves setting the selected value in a dropdown through an enum property in a view model which is bound from the querystring (it's probaly clearer what I'm talking about if you just read the code):
Controller/Model
public class HomeController : Controller
{
public ActionResult Index(TestModel model)
{
return View(model);
}
}
public class TestModel
{
public SelectList EnumOptions { get; set; }
public TestEnum EnumValue { get; set; }
public TestModel()
{
var options = from Enum e in Enum.GetValues(typeof(TestEnum))
select new { Value = e, Name = e.ToString() };
EnumOptions = new SelectList(options, "Value", "Name", TestEnum.NotSet);
}
}
public enum TestEnum
{
NotSet = 0,
Dog = 1,
Cat = 2
}
View
#Html.DropDownListFor(m => m.EnumValue, Model.EnumOptions)
Dog numeric
Dog string
It's all pretty simple.
The question is, why doesn't the second "Dog" link work properly? Note it submits the enumValue as a numeric property, instead of as a "string" property.
But the model binder has no problem with this. The model supplied to the View is exactly the same in either case. So how does the dropdown selected value get rendered correctly in one case but not the other?
DropDownListFor looks into the modelstatedictionary for getting the current value of the field/property.
The ValueProviderResult for the second link has a value from 1.
The modelbinder knows that the requested type is a TestEnum. A 1 can be converted to Dog.
The dropwdownlist converts the value of the ValueProviderResult into a string. A 1 converts to "1" as a string. There is no entry in the selectlist with a value of "1".
Therefor the dropdownlist has a wrong current value.

Resources