Binding ObservableCollection to datagrid in MVVM - mvvm

Im new to XAML, i am trying to bind ObservableCollection to datagrid in MVVM.
I want to get notified when CollectionChanged. But its throwing null exception.
Please let me know when im going wrong. Thanks in advance.
The following is Code behind for viewModel:
public class MainwindowViewModel : INotifyPropertyChanged
{
MyObject myObj;
ObservableCollection<MyObject> _ocObj;
public MainwindowViewModel()
{
_ocObj = new ObservableCollection<MyObject>();
myObj = new MyObject();
myObj.ID = 0;
myObj.Name = "Name";
_ocObj.Add(myObj);
_ocObj.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_ocMyobj_CollectionChanged);
}
void _ocMyobj_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Windows.MessageBox.Show("propeties changed # " + e.NewStartingIndex.ToString()
+ " old items starting # " + e.OldStartingIndex + " olditems count " + e.OldItems.Count.ToString()
+ " action " + e.Action.ToString());
}
public ObservableCollection<MyObject> ocObj
{
get { return _ocObj; }
set
{
PropertyChanged(this, new PropertyChangedEventArgs("ocMyobj"));
}
}
public string Name
{
get { return myObj.Name; }
set
{
if (value !=null)
{
myObj.Name = value;
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
public int ID
{
get { return myObj.ID; }
set
{
if (myObj.ID != value)
{
myObj.ID = value;
PropertyChanged(this, new PropertyChangedEventArgs("ID"));
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class MyObject
{
public string Name { get; set; }
public int ID { get; set; }
}
Below is XAML:
<Window.Resources>
<vm:MainwindowViewModel x:Key="someObj"/>
</Window.Resources>
<DataGrid ItemsSource="{Binding ocObj}" DataContext="{Binding Source={StaticResource someObj}}" AutoGenerateColumns="True" />

Take a look at the documentation on the NotifyCollectionChangedEventArgs class. Note that the OldItems object only "Gets the list of items affected by a Replace, Remove, or Move action." What this means is that for other actions, OldItems will be null.
Therefore, if an Add action is performed against your ObservableCollection, OldItems is null (and valid). Just perform a check for it in your code, such as:
System.Windows.MessageBox.Show("propeties changed # " + e.NewStartingIndex.ToString()
+ " old items starting # " + e.OldStartingIndex + " olditems count " +
(e.OldItems == null ? "0" : e.OldItems.Count.ToString())
+ " action " + e.Action.ToString());

Related

Update SyncFusion Grid after backend data source has changed

I have a SyncFusion data grid tied to a backend SQL database. My crud actions are called through custom buttons that call a dialog box.
This works nicely except that the grid is not updated with the backend data after an add/edit/delete. I have tired refreshing the grid but that doesn't seem to work.
What do I need to do?
MyTemplates.razor
#page "/My_Templates"
#using WireDesk.Models
#inject IWireDeskService WireDeskService
<ReusableDialog #ref="dialog"></ReusableDialog>
<SfGrid #ref="Grid" DataSource="#Templates" TValue="Template" AllowSorting="true" Toolbar="ToolbarItems">
<GridEvents OnToolbarClick="OnClicked" TValue="Template"></GridEvents>
<GridColumns>
<GridColumn Field=#nameof(Template.Owner) HeaderText="Owner" ValidationRules="#(new ValidationRules { Required = true })" Width="120"></GridColumn>
<GridColumn Field=#nameof(Template.Users) HeaderText="Users" TextAlign="TextAlign.Left" Width="130"></GridColumn>
<GridColumn Field=#nameof(Template.Description) HeaderText="Description" TextAlign="TextAlign.Left" Width="130"></GridColumn>
<GridColumn Field=#nameof(Template.FundType) HeaderText="Fund Type" TextAlign="TextAlign.Left" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
#code{
//Instantiate objects
SfGrid<Template> Grid { get; set; }
ReusableDialog dialog;
//Instantiate toolbar and toolbar items
private List<Object> ToolbarItems = new List<Object>()
{
new ItemModel() { Text = "Create New Template", TooltipText = "Add", PrefixIcon = "e-icons e-update", Id = "Add", },
new ItemModel() { Text = "Edit Template", TooltipText = "Edit", PrefixIcon = "e-icons e-update", Id = "Edit"}
};
//Instatiate records
public IEnumerable<Template> Templates { get; set; }
//Instantiate Records
protected override void OnInitialized()
{
Templates = WireDeskService.GetTemplates();
}
//Handle toolbar clicks
public async Task OnClicked(Syncfusion.Blazor.Navigations.ClickEventArgs Args)
{
//Create Record
if (Args.Item.Id == "Add")
{
Args.Cancel = true; //Prevent the default action
dialog.Title = "This is the Add Title";
dialog.Text = "This is the add text";
dialog.template = new Template();
dialog.OpenDialog();
WireDeskService.InsertTemplate(dialog.template);
//Grid.CallStateHasChanged(); Doesn't Work
//Templates = WireDeskService.GetTemplates(); Doesn't Work
}
//Edit Records
if (Args.Item.Id == "Edit")
{
Args.Cancel = true; //Prevent the default action
var selected = await Grid.GetSelectedRecordsAsync();
if (selected.Count > 0)
{
//Call Dialog Box Here
dialog.Title = "This is the Edited Title";
dialog.Text = "This is the edited text";
dialog.template = selected[0];
dialog.OpenDialog();
WireDeskService.UpdateTemplate(dialog.template.TemplateId, dialog.template);
Grid.CallStateHasChanged();
}
}
}
}
<style>
.e-altrow {
background-color: rgb(182 201 244);
}
</style>
WireDeskService.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
namespace WireDesk.Models
{
public class WireDeskService : IWireDeskService
{
private WireDeskContext _context;
public WireDeskService(WireDeskContext context)
{
_context = context;
}
public void DeleteTemplate(long templateId)
{
try
{
Template ord = _context.Templates.Find(templateId);
_context.Templates.Remove(ord);
_context.SaveChanges();
}
catch
{
throw;
}
}
public IEnumerable<Template> GetTemplates()
{
try
{
return _context.Templates.ToList();
}
catch
{
throw;
}
}
public void InsertTemplate(Template template)
{
try
{
_context.Templates.Add(template);
_context.SaveChanges();
}
catch
{
throw;
}
}
public Template SingleTemplate(long id)
{
throw new NotImplementedException();
}
public void UpdateTemplate(long templateId, Template template) {
try
{
var local = _context.Set<Template>().Local.FirstOrDefault(entry => entry.TemplateId.Equals(template.TemplateId));
// check if local is not null
if (local != null)
{
// detach
_context.Entry(local).State = EntityState.Detached;
}
_context.Entry(template).State = EntityState.Modified;
_context.SaveChanges();
}
catch
{
throw;
}
}
void IWireDeskService.SingleTemplate(long templateId)
{
throw new NotImplementedException();
}
}
}
We have analyzed your query and we understand that you want to save the changes in your database when data is bound to Grid using DataSource property. We would like to inform you that when data is bound to Grid component using DataSource property, CRUD actions needs to handled using ActionEvents (OnActionComplete and OnActionBegin).
OnActionBegin event – Will be triggered when certain action gets initiated.
OnActionComplete event – Will be triggered when certain action gets completed.
We suggest you to achieve your requirement to save the changes in database using OnActionBegin event of Grid when RequestType is Save. While saving the records, irrespective of Add or Update action. OnActionBegin event will be triggered when RequestType as Save. In that event we can update the changes into database.
Since the Add and Edit actions share the same RequestType “Save”, we can differentiate the current action using Args.Action argument. Similarly we request you fetch the updated data from your database and bind to Grid in OnActionComplete event of Grid.
Refer the below code example.
<SfGrid #ref="Grid" DataSource="#GridData" Toolbar="#(new List<string> { "Add", "Edit", "Delete", "Cancel", "Update" })" AllowFiltering="true" AllowSorting="true" AllowPaging="true">
<GridEditSettings AllowAdding="true" AllowDeleting="true" AllowEditing="true"></GridEditSettings>
<GridEvents OnActionBegin="OnBegin" OnActionComplete="OnComplete" TValue="Order"></GridEvents>
<GridColumns>
<GridColumn Field=#nameof(Order.OrderID) HeaderText="Order ID" IsIdentity="true" IsPrimaryKey="true" TextAlign="TextAlign.Right" Width="120"></GridColumn>
<GridColumn Field=#nameof(Order.CustomerID) HeaderText="Customer Name" Width="150"></GridColumn>
<GridColumn Field=#nameof(Order.EmployeeID) HeaderText="Id" Width="150"></GridColumn>
</GridColumns>
</SfGrid>
#code{
SfGrid<Order> Grid { get; set; }
public IEnumerable<Order> GridData { get; set;}
protected override void OnInitialized()
{
GridData = OrderData.GetAllOrders().ToList();
}
public void OnComplete(ActionEventArgs<Order> Args)
{
if (Args.RequestType == Syncfusion.Blazor.Grids.Action.Save || Args.RequestType == Syncfusion.Blazor.Grids.Action.Refresh)
{
GridData = OrderData.GetAllOrders().ToList(); // fetch updated data from service and bind to grid datasource property
}
}
public void OnBegin(ActionEventArgs<Order> Args)
{
if (Args.RequestType == Syncfusion.Blazor.Grids.Action.Save) // update the changes in Actionbegine event
{
if (Args.Action == "Add")
{
//Args.Data contain the inserted record details
//insert the data into your database
OrderData.AddOrder(Args.Data);
}
else
{
//Args.Data contain the updated record details
//update the data into your database
OrderData.UpdateOrder(Args.Data);
}
} else if (Args.RequestType == Syncfusion.Blazor.Grids.Action.Delete)
{
OrderData.DeleteOrder(Args.Data.OrderID); // delete the record from your database
}
}
}
Refer our UG documentation for your reference
https://blazor.syncfusion.com/documentation/datagrid/events/#onactionbegin
https://blazor.syncfusion.com/documentation/datagrid/events/#onactioncomplete

Open Perspective programmatically

I am trying to provide a command/ handler to switch to a specific Perspective.
I came up with the following class:
public class OpenPerspectiveHandler {
private static final Logger logger = Logger.getLogger(OpenPerspectiveHandler.class);
#Inject
private MApplication application;
#Inject
private EModelService modelService;
#Inject
private EPartService partService;
private final String perspectiveId;
public OpenPerspectiveHandler(String perspectiveId) {
super();
this.perspectiveId = perspectiveId;
}
public void changePerspective(String perspectiveId) {
Optional<MPerspective> perspective = findPerspective();
if(perspective.isPresent()) {
partService.switchPerspective(perspective.get());
} else {
logger.debug("Perspective not found (" + perspectiveId + ")");
}
}
#Execute
public void execute() {
changePerspective(perspectiveId);
}
private Optional<MPerspective> findPerspective() {
MUIElement element = modelService.find(perspectiveId, application);
if(element instanceof MPerspective) {
return Optional.of((MPerspective)element);
} else {
logger.debug("Wrong type " + element);
}
return Optional.empty();
}
#Override
public String toString() {
return "OpenPerspectiveHandler [application=" + application + ", modelService=" + modelService + ", partService=" + partService + ", perspectiveId=" + perspectiveId + "]";
}
}
Interestingly, this works only once. A workaround is to cache MPerspective once it was found and not to use modelService.find(perspectiveId, application) again.
Why does it work only once? modelService.find(perspectiveId, application) returns null after the first execution.
EDIT:
Another approach (as suggested by greg-449) is the following:
public class OpenPerspectiveHandler {
private static final Logger logger = Logger.getLogger(OpenPerspectiveHandler.class);
private final String perspectiveId;
public OpenPerspectiveHandler(String perspectiveId) {
super();
this.perspectiveId = perspectiveId;
}
#Execute
public void changePerspective(MApplication application, EModelService modelService, EPartService partService) {
Optional<MPerspective> perspective = findPerspective(application, modelService);
if(perspective.isPresent()) {
partService.switchPerspective(perspective.get());
} else {
logger.debug("Perspective not found (" + perspectiveId + ")");
}
}
private Optional<MPerspective> findPerspective(MApplication application, EModelService modelService) {
MUIElement element = modelService.find(perspectiveId, application);
if(element instanceof MPerspective) {
return Optional.of((MPerspective)element);
} else {
logger.debug("Wrong type " + element);
}
return Optional.empty();
}
}
But this approach also changes the perspective only once. modelService.find(perspectiveId, application); returns null after the first execution.
The EPartService varies depending on the context that the handler runs in. In some cases you get the Application part service which only works if there is an active window.
You can get the part service for that window using something like:
MWindow window = (MWindow)modelService.find("top window id", application);
IEclipseContext windowContext = window.getContext();
EPartService windowPartService = windowContext.get(EPartService.class);

Mybatis can set value with reflection?

Mybatis can set value with reflection?
I have a class , and it has a property , it's setter is protected . So I have to use reflection to set this value ? Mybatis can work ?
yes, mybatis use reflect to set value.
in Reflator.java(mybatis 3.3.0), mybatis will config set method.
private void addSetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
String name = method.getName();
if (name.startsWith("set") && name.length() > 3) {
if (method.getParameterTypes().length == 1) {
name = PropertyNamer.methodToProperty(name);
addMethodConflict(conflictingSetters, name, method);
}
}
}
resolveSetterConflicts(conflictingSetters);
}
if your class do not have set method, when addSetFields it will add new SetInvoker for the field:
private void addSetField(Field field) {
if (isValidPropertyName(field.getName())) {
setMethods.put(field.getName(), new SetFieldInvoker(field));
setTypes.put(field.getName(), field.getType());
}
}
and the SetFieldInvoker is like this:
/**
* #author Clinton Begin
*/
public class SetFieldInvoker implements Invoker {
private Field field;
public SetFieldInvoker(Field field) {
this.field = field;
}
#Override
public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
field.set(target, args[0]);
return null;
}
#Override
public Class<?> getType() {
return field.getType();
}
}
the DefaultResultSetHandler calls BeanWrapper's setBeanProperty method will call getSetInvoker
private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
try {
Invoker method = metaClass.getSetInvoker(prop.getName());
Object[] params = {value};
try {
method.invoke(object, params);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} catch (Throwable t) {
throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
}
}
the whole call chain maybe like this:
DefaultSqlSession##selectList -->SimpleExecutor##doQuery --> SimpleStatementHandler##query --> DefaultResultSetHandler##handleResultSets

C# IEnumerable Interface

I am trying to understand this example from a MSDN article, to my understanding with the IEnumberable interface, we will be able to use Foreach to loop through the class collection, I am confused at the Main method, why don't we just use:
foreach (Person p in peopleArray)
Console.WriteLine(p.firstName + " " + p.lastName);
instead of
People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
Console.WriteLine(p.firstName + " " + p.lastName);
Example:
using System;
using System.Collections;
public class Person
{
public Person(string fName, string lName)
{
this.firstName = fName;
this.lastName = lName;
}
public string firstName;
public string lastName;
}
public class People : IEnumerable
{
private Person[] _people;
public People(Person[] pArray)
{
_people = new Person[pArray.Length];
for (int i = 0; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator) GetEnumerator();
}
public PeopleEnum GetEnumerator()
{
return new PeopleEnum(_people);
}
}
public class PeopleEnum : IEnumerator
{
public Person[] _people;
// Enumerators are positioned before the first element
// until the first MoveNext() call.
int position = -1;
public PeopleEnum(Person[] list)
{
_people = list;
}
public bool MoveNext()
{
position++;
return (position < _people.Length);
}
public void Reset()
{
position = -1;
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public Person Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
}
class App
{
static void Main()
{
Person[] peopleArray = new Person[3]
{
new Person("John", "Smith"),
new Person("Jim", "Johnson"),
new Person("Sue", "Rabon"),
};
People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
Console.WriteLine(p.firstName + " " + p.lastName);
}
}
You're right, you could simply use the first version because arrays implement IEnumerable.
The reason they chose to iterate over People is simply for academic purposes; to demonstrate how iterators work (and how to implement IEnumerable). If they simply iterated over peoplearray, they wouldn't be using the People class, which is the main focus of that example.

primefaces (3.2) autocomplete pojo

I am trying to implement PF Autocomplete component (v3.2) with POJO. Things work fine when a user selects an entry from the possible drop down options provided. However, the scenario where a user enters some text into the input field and simply hits enter does not work well for me.
If a user simply enters some text to the autocomplete input box and hits enter, all I have to do is redirect them to a new page with 1 query parameter. I noticed that if I hit enter before AutoComplete has a chance to show suggestions, then the redirect happens just fine. Once drop down suggestions are shown, hitting enter simply clears the input box.
I would expect something like this to work just fine, so I must be doing something silly.
<h:form>
<p:autoComplete id="globalAutoComplete" value="#{autoCompleteBackingBean.selectedResult}" completeMethod="#{autoCompleteBackingBean.globalSearch}" var="aResult" itemValue="#{aResult}" converter="autoCompleteConverter" queryDelay="200" maxResults="6" minQueryLength="3">
<p:column>
#{aResult.label} <br/> #{aResult.desc} <span class='ui-icon #{aResult.icon} autocompleteResultsIcon'/>
</p:column>
</p:autoComplete>
<p:commandButton id="submitAutoComplete" type="submit" icon="ui-icon-suitcase" action="#{autoCompleteBackingBean.doRedirect}"/>
</h:form>
Converter:
public Object getAsObject(FacesContext fc, UIComponent uic, String value) {
if (value == null || value.length() == 0 ) {
return null;
}
Object obj = null;
try {
List<AutoComplete> results = AutoCompleteBackingBean.getResults();
for (AutoComplete aResult : results){
if((aResult.getDreRef()).equals(value))
return aResult;
}
} catch (Exception e) {
System.err.println("AutoCompleteConverter getAsObject Error: " + e);
}
if (obj == null) {
AutoComplete empty = new AutoComplete();
empty.setDreRef(value);
return empty;
}
return obj;
}
public String getAsString(FacesContext fc, UIComponent uic, Object object) {
if (object == null) {
return null;
}
Class entityClass = getEntityClass();
String value = null;
if (entityClass.isInstance(object)) {
try {
value = ((AutoComplete)object).getDreRef();
}catch (Exception e){
System.err.println("AutoCompleteConverter getAsString Error: " + e);
value = "Unable to obtain String from Object";
}
}
else {
value = "AutoCompleteConverter.getAsString(): Object " + object + " is of type "
+ object.getClass().getSimpleName() + "; expected type: "
+ entityClass.getSimpleName();
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, value, null));
}
return value;
}
Backing bean:
private AutoComplete selectedResult = null;
//getter and setter
public String doRedirect() {
System.out.println("doRedirect()");
return "entities.jsf?query=" + selectedResult.getDreRef() + "&faces-redirect=true";
}