WPF Binding issue if i call the code from other UC - c#

Hi am having a problem to understand binding in WPF.
I have got a User Control and contains ListView, and reading data from database but it takes a minimum of 60 seconds and then fill the listview with data.
There is a status bar which shows the loading process.
If data is loaded in the memory and User opens the UC, View model loads the data to ListView. Everything works fine.
But if User opens the UC before data read finish and at the end of the reading data i call the same method in the viewModel, in the code behind data is loaded to List item but ListView is still empty. Somehow ListView doesn't show data.
<ListView x:Name="ListViewUK" ItemsSource="{Binding ListOfAccountsFromExchUK}" >
View Model
public class ExchequerViewModel :BaseView
{
List<Exch_Account> exch_Accounts_UK;
public ObservableCollection<PAMHeaderModel> ListOfAccountsFromExchUK { get; set; }
#region CONSTRUCTOR
public ExchequerViewModel()
{
ListOfAccountsFromExchUK = new ObservableCollection<PAMHeaderModel>();
PopulateExchequerList();
}
#endregion
public void PopulateExchequerList()
{
exch_Accounts_UK = ExchequerMemory.ExcAccountList_UK;
if (exch_Accounts_UK == null)
{
AutoClosingMessageBox.Show("Exchequer UK - datas are loading Pleae try again later.", "Information", 2000);
}
if (exch_Accounts_UK != null)
{
UmbList = new Dictionary<string, string>();
foreach (var acc in exch_Accounts_UK)
{
ListOfAccountsFromExchUK.Add(new PAMHeaderModel
{
Company = "UK",
RefNo = acc.Code,
Name = acc.Company,
Subsidiary = acc.UDF6
});
UmbList.Add(acc.Code, acc.UDF6); ? acc.UDF6 : "";
}
if (ListOfAccountsFromExchUK != null)
UKStatusInfoLabel = ListOfAccountsFromExchUK.Count();
}
User Control
public ExchequerViewModel viewModel;
public ExchequereUC()
{
InitializeComponent();
viewModel = new ExchequerViewModel();
DataContext = viewModel;
}

Related

Xamarin Picker binding

Am I missing something or is there more to it that I am not getting? I'm working on a mobile app and have to use pickers for choices from a data table. To start, I have many such pickers that are key/value based. I have an internal ID and a corresponding Show value. The IDs do not always have 1, 2, 3 values such as originating from a lookup table and may have things as
KeyID / ShowValue
27 = Another Thing
55 = Many More
12 = Some Item
Retrieved as simple as
select * from LookupTable where Category = 'demo'
So I have this class below that is used for binding the picker via a list of records
public class CboIntKeyValue
{
public int KeyID { get; set; } = 0;
public string ShowValue { get; set; } = "";
}
Now, the data record that I am trying to bind to has only the ID column associated to the lookup. Without getting buried into XAML, but in general, I have my ViewModel. On that I have an instance of my data record that has the ID column.
public class MyViewModel : BindableObject
{
public MyViewModel()
{
// Sample to pre-load list of records from data server of KVP
PickerChoices = GetDataFromServerForDemo( "select * from LookupTable where Category = 'demo'" );
ShowThisRecord = new MyDataRec();
// for grins, I am setting the value that SHOULD be defaulted
// in picker. In this case, ID = 12 = "Some Item" from above
ShowThisRecord.MyID = 12;
}
// this is the record that has the "ID" column I am trying to bind to
public MyDataRec ShowThisRecord {get; set;}
// The picker is bound to this list of possible choices
public List<CboIntKeyValue> PickerChoices {get; set;}
}
I can’t bind to the index of the list, because that would give me 0, 1, 2, when I would be expecting the corresponding "ID" to be the basis of proper record within the list.
In WPF, I have in the past, been able to declare the show value for the screen, but also the bind value to the ID column in similar. So, the binding of the INT property on my "ShowThisRecord" would drive and properly refresh.
I can see the binding of SelectedItem, but that is the whole item of the KVP class which is not part of the MyDataRec. Only the ID is the common element between them.
What is the proper bindings to get this to work?
<Picker ItemDisplayBinding="{Binding ShowValue}"
SelectedItem="{Binding ???}" />
Just to confirm my record bindings are legitimate, my page has binding context to MyViewModel as I can properly see the ID via a sample text entry I added to the page via.
<Entry Text="{Binding Path=ShowThisRecord.MyID}"/>
I created a demo to test your code, and it works properly. The full demo is here. I also added a function to verify the selected item.
If you want to get the SelectedItem object synchronously, the MyViewModel should implement INotifyPropertyChanged, and I created a selectedRecordfield for SelectedItem, so you can do like this:
public class MyViewModel : ViewModelBase
{
public MyViewModel()
{
// Sample to pre-load list of records from data server of KVP
//PickerChoices = GetDataFromServerForDemo("select * from LookupTable where Category = 'demo'");
PickerChoices = new ObservableCollection<TestModel>() {
new TestModel{MyID = 5, ShowValue="test1"}, new TestModel{MyID = 9, ShowValue="test2"},
new TestModel{MyID = 18, ShowValue="test18"}, new TestModel{MyID = 34, ShowValue="test4"}
};
// Set the default selected item
// foreach (TestModel model in PickerChoices) {
// if (model.MyID == 18) { // Default value
// SelectedRecord = model;
// break;
// }
// }
ShowThisRecord = new TestModel();
// For grins, I am setting the value that SHOULD be defaulted
// in picker. In this case, ID = 12 = "Some Item" from above
ShowThisRecord.MyID = 12;
}
// This is the record that has the "ID" column I am trying to bind to
public TestModel ShowThisRecord { get; set; }
//*****************************************
TestModel selectedRecord; // Selected item object
public TestModel SelectedRecord
{
get { return selectedRecord; }
set
{
if (selectedRecord != value)
{
selectedRecord = value;
OnPropertyChanged();
}
}
}
//*****************************************
// The picker is bound to this list of possible choices
public ObservableCollection<TestModel> PickerChoices { get; set; }
}
class ViewModelBase
public class ViewModelBase: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And the XAML content:
<Picker Title="Select a value" x:Name="mypicker"
ItemsSource="{Binding Path= PickerChoices}"
SelectedItem="{Binding SelectedRecord}"
ItemDisplayBinding="{Binding MyID}"/>
File xaml.cs:
public partial class MainPage : ContentPage
{
ObservableCollection<TestModel> items = new ObservableCollection<TestModel>();
MyViewModel testModel = null;
public MainPage()
{
InitializeComponent();
testModel = new MyViewModel();
BindingContext = testModel;
// This will also work
//if (testModel!=null && testModel.PickerChoices!=null) {
// for (int index=0; index< testModel.PickerChoices.Count; index++) {
// TestModel temp = testModel.PickerChoices[index];
// if (18 == temp.MyID) {
// mypicker.SelectedIndex = index;
// break;
// }
// }
//}
foreach (TestModel model in testModel.PickerChoices)
{
if (model.MyID == 18)
{ // Default value
testModel.SelectedRecord = model;
break;
}
}
}
// To show the selected item
private void Button_Clicked(object sender, EventArgs e)
{
if (testModel.SelectedRecord!=null) {
DisplayAlert("Alert", "selected Item MyID: " + testModel.SelectedRecord.MyID + "<--> ShowValue: " + testModel.SelectedRecord.ShowValue, "OK");
}
}
}
The result is:
You need to set the ItemsSource property to your list of CboIntValue items:
<Picker Title="Select a value"
ItemsSource="{Binding PickerChoices}"
ItemDisplayBinding="{Binding ShowValue}" />
After much work, I ended up writing my own separate class and template style for what I needed. Due to the length of it, and posting the source code for anyone to use, review, assess, whatever, I posted that out on The Code Project.
Again, the primary issue I had is if I have an integer key ID coming from a data source, the picker would not automatically refresh itself by just the given ID (int or string).

How to Refresh or Recall Page on Xamarin Forms

So, I have this app where I can choose a car and see the car info... I'm displaying the cars like this.
I'm using the Rg.Plugins.Popup so when I click the car icon, it opens this popup with "my cars"
So now I'm facing a problem which is, when I choose a car, I want to refresh my current page so the car's info can be shown... I'm handling the car button click on this next view model:
public class MyCarViewModel : ViewModelBase
{
public MyCarViewModel()
{
}
public MyCarViewModel(INavigation navigation)
{
this.Navigation = navigation;
this.SelectedCar = null;
GetClientCars();
}
private Page page { get; set; }
private List<CarInfo> _CarList;
public List<CarInfo> CarList
{
get
{
return _CarList;
}
set
{
_CarList = value;
OnPropertyChanged("CarList");
}
}
private CarInfo _SelectedCar;
public CarInfo SelectedCar
{
get
{
return _SelectedCar;
}
set
{
_SelectedCar = value;
OnPropertyChanged("SelectedCar");
if (_SelectedCar != null)
{
CarSelected(_SelectedCar);
}
}
}
public INavigation Navigation { get; set; }
private void CarSelected(CarInfo car)
{
App.choosedCar = car;
PopupNavigation.Instance.PopAllAsync();
this.SelectedCar = null;
}
}
And I want this View to refresh
<views:BaseMainPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="OficinaDigitalX.Views.CarDetails"
xmlns:views="clr-namespace:OficinaDigitalX.Views">
<views:BaseMainPage.Content>
<StackLayout>
<Label Text="{Binding VID, StringFormat='Modelo: {0:F0}'}" FontAttributes="Bold"
FontSize="Large"/>
<Label Text="{Binding LicencePlate, StringFormat='Matrícula: {0:F0}'}"/>
<Label Text="{Binding Chassis, StringFormat='Chassis: {0:F0}'}"/>
<Label Text="{Binding Km, StringFormat='Ultimos Km Registados: {0:N0}'}"/>
</StackLayout>
</views:BaseMainPage.Content>
</views:BaseMainPage>
and xaml.cs
public partial class CarDetails : BaseMainPage
{
public CarDetails(CarInfo car)
{
InitializeComponent();
BindingContext = new CarDetailViewModel(this);
App.currentPage = this;
if (car != null)
{
this.Title = "Dados de " + car.MakerandModel;
}
else
{
this.Title = "Escolha uma Viatura";
}
}
}
I'm facing a lot of issues here because my car icon is a part of my "BaseMainPage" which is extended by the other Views (so the icon can be shown on all views)...
So when I click the button, the application doesn't know its current page...
I thought I might use the Navigation Stack to reload it but I don't quite know how to do this...
Hope you guys can help
Well, essentially you do not need to refresh page or reload page, you just need to refresh the data.
since you are using OnPropertyChanged(INotifyPropertyChanged) you are half way there.
instead of using List CarList use ObservableCollection CarList.
and if you deliberately want to reload the page, on dismissing the pop.up save your data and call the constructor/reinitiate the Page.
hopefully you should achieve what you are looking for.
I think you don't need to reload the page, you need to reload your data. Your page will be updated automatically with the databindings.
For me it looks like you're using Prism, so you could override the OnNavigatingTo Method and load the data every time the page is "opened".
I've just used MessagingCenter and I've called it with my OnPropertyChanged and this seemed to do the work! Thanks a lot!
View Model:
OnPropertyChanged("SelectedCar");
if (_SelectedCar != null)
{
CarSelected(_SelectedCar);
MessagingCenter.Send(this, "Hi");
}
My other view model's constructor
MessagingCenter.Subscribe<MyCarViewModel>(this, "Hi", (sender) => {
this.currentCar = App.choosedCar;
});

Append a Row to a Datagrid in WPF using MVVM

I have a DataGrid in my View as shown below.,
My Question is how can I Append the values from the textboxes to the row datagrid
I have make sure that the Model has All the properties, When I click on the Add button it overwrites the dataGrid and shows only one latest record the and my ViewModel look like this:
class BatchItemsViewModel : ViewModelBase
{
public SearchItemsModel msearchItems { get; set; }
ObservableCollection<SearchItemsModel> _BatchItemsGrid;
public ObservableCollection<SearchItemsModel> BatchItemsGrid
{
get { return _BatchItemsGrid; }
set
{
_BatchItemsGrid = value;
OnPropertyChanged("BatchItemsGrid");
}
}
private ICommand _addDataToBatchGrid;
public ICommand addDataToBatchGrid
{
get
{
return _addDataToBatchGrid;
}
set
{
_addDataToBatchGrid = value;
}
}
public BatchItemsViewModel()
{
msearchItems = new SearchItemsModel();
addDataToBatchGrid = new RelayCommand(new Action<object>(AddDataInBatchGrid));
}
public void AddDataInBatchGrid(object obj)
{
ObservableCollection<SearchItemsModel> batchGridData = new ObservableCollection<SearchItemsModel>();
var data = new SearchItemsModel
{
BatchNumber = msearchItems.BatchNumber,
MFDDate = msearchItems.MFDDate,
ExpiryDate = msearchItems.ExpiryDate,
Quantity = msearchItems.Quantity,
};
batchGridData.Add(data);
BatchItemsGrid = batchGridData; // HERE I am overwriting the datagrid
//How can I Append the batchGridData to BatchItemsGrid (BatchItemsGrid.Append(batchGridData)???)
}
}
NOTE: I have gone through the other threads as well in the community for the similar posts but I couldn't find the appropriate and please correct me if I am going in wrong direction.
public void AddDataInBatchGrid(object obj)
{
var data = new SearchItemsModel
{
BatchNumber = msearchItems.BatchNumber,
MFDDate = msearchItems.MFDDate,
ExpiryDate = msearchItems.ExpiryDate,
Quantity = msearchItems.Quantity,
};
this.BatchItemsGrid.Add(data);
}
...Should do the trick. (don't replace the whole collection, just add items to it and let the notification events handle the UI updates)

How to update list box items with a timer

I'm working on a messenger program and I have a timer which constantly deletes and adds new list box items so the list box flickers all the time. I'm trying to make the flickering stop. The reason I'm constantly deleting and adding new list box items is because if a friend logs in, it will change there status from offline to online.
Timer code:
private void Requests_Tick(object sender, EventArgs e)
{
LoadData();
}
LoadData() code:
FriendsLb.BeginUpdate();
_S = new Status();
Image Status = null;
FriendsLb.Items.Clear();
try
{
var query = from o in Globals.DB.Friends
where o.UserEmail == Properties.Settings.Default.Email
select new
{
FirstName = o.FirstName,
LastName = o.LastName,
Email = o.Email,
Status = o.Status,
Display = string.Format("{0} {1} - ({2})", o.FirstName, o.LastName, o.Email)
};
newFriendsLb.DataSource = query.ToList();
newFriendsLb.ClearSelected();
FriendsLb.DrawMode = DrawMode.OwnerDrawVariable;
foreach (object contact in query.ToList())
{
string details = contact.GetType().GetProperty("Display").GetValue(contact, null).ToString();
string email = contact.GetType().GetProperty("Email").GetValue(contact, null).ToString();
string status = _S.LoadStatus(email);
if (status == "Online")
{
Status = Properties.Resources.online;
}
else if (status == "Away")
{
Status = Properties.Resources.busy;
}
else if (status == "Busy")
{
Status = Properties.Resources.away;
}
else if (status == "Offline")
{
Status = Properties.Resources.offline;
}
FriendsLb.Items.Add(new Listbox(_A.LoadFriendAvatar(email), Status, details));
}
contact = query.ToList();
FriendsLb.MeasureItem += FriendsLb_MeasureItem;
FriendsLb.DrawItem += FriendsLb_DrawItem;
FriendsLb.EndUpdate();
Is there a way to update the current list box items constantly rather than constantly deleting and adding new ones?
Here's the GUI:
The are several ways to remove the flicker - all basically involve not completely repopulating the list each time. For this, you want to get the current status for the users and simply update the existing list.
In order for the control to see changes to the list items, rather than an anonymous type, you need a User class so that you can implement INotifyPropertyChanged. This "broadcasts" a notice that a property value has changed. You will also need to use a BindingList<T> so those messages get forwarded to the control. This will also allow additions/deletions from the list to be reflected.
You will also need a concrete way to find each user, so the class will need some sort of ID.
public enum UserStatus { Unknown, Online, Offline, Away, Busy }
class User : INotifyPropertyChanged
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Image StatusImage;
private UserStatus status = UserStatus.Unknown;
public UserStatus Status
{
get{return status;}
set{
if (value != status)
{
status=value;
PropertyChanged(this, new PropertyChangedEventArgs("Status"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public override string ToString()
{
return string.Format("{0}, {1}: {2}", LastName, FirstName, Status);
}
}
Then the collection:
private BindingList<User> Users;
private Image[] StatusImgs; // See notes
The BindingList is then used as the DataSource for the control:
Users = GetUserList();
// display the list contents in the listbox:
lbUsers.DataSource = Users;
timer1.Enabled = true;
Updating the user status just involves resetting the Status on each user which has changed. The BindingList<User> will then notify the control to update the display:
private void UpdateUserStatus()
{
// get current list of user and status
var newStatus = GetCurrentStatus();
User thisUser;
// find the changed user and update
foreach (User u in newStatus)
{
thisUser = Users.FirstOrDefault(q => q.Id == u.Id);
// ToDo: If null, there is a new user in the list: add them.
if (thisUser != null && thisUser.Status != u.Status)
{
thisUser.Status = u.Status;
thisUser.StatusImage = StatusImgs[(int)u.Status];
}
}
}
Results:
Note that there is a potential leak in your app. If you drill into the code to get an image from Resources you will see:
internal static System.Drawing.Bitmap ball_green {
get {
object obj = ResourceManager.GetObject("ball_green", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
GetObject() is creating a new object/image each time you call it, your code doesnt show the old one being Disposed() so, it is likely leaking resources.
Since each online user doesn't need their own unique instance (or a new one when the status changes), load them once into a List or array so they can be reused:
// storage:
private Image[] StatusImgs;
...
// populate:
StatusImgs = new Image[] {Resources.ball_black, Resources.ball_green,
Resources.ball_red, Resources.ball_yellow, Resources.ball_delete};
...
// usage:
thisUser.StatusImage = StatusImgs[(int)u.Status];
You could also change it so the User class updates that itself when the Status changes.
Finally, you might want to consider a simple UserControl for the UI rather than what appears to be an owner drawn Listbox.
If you don't want to change your code structure to eliminate the repeated Clear/Reload cycle, you should suspend UI drawing while you are rebuilding your list using;
using(var d = Dispatcher.DisableProcessing())
{
/* your work... */
}
As suggested here In WPF, what is the equivalent of Suspend/ResumeLayout() and BackgroundWorker() from Windows Forms

Binding ObservableCollection take much time in fact the window take much time to be displayed

I'm using MVVM light Framework with WPF and I have a DataGrid that contain all the customers loaded from my SQLite database, But it take too much time to display the Window so if any one can help me for I can dislpay the window and load the DataGrid separately.I think that the Window is taking time because of the DataGrid Binding.
public ObservableCollection<CustumerModel> customerList
{
get
{
_customerList = new ObservableCollection<CustumerModel>();
IList<CustumerModel> listCustomer = RemplireListCustomer();
_customerList = new ObservableCollection<CustumerModel>(listCustomer);
return _customerList;
}
the method RemplireListCustomer
private IList<CustumerModel> RemplireListCustomer()
{
IList<CustumerModel> listCustomer = new List<CustumerModel>();
foreach (var c in _customerService.GetAllCustomers())
{
listCustomer.Add((CustumerModel)c);
}
return listCustomer;
}
You could load your data async by starting a new Task in e.g. your class' constructor.
public class YourClass
{
public YourClass()
{
TaskEx.Run(() =>
{
var listCustomer = RemplireListCustomer();
CustomerList = new ObservableCollection<CustumerModel>(listCustomer);
});
}
public ObservableCollection<CustumerModel> CustomerList { get; private set; }
}
And just maybe you do not have to iterate over all customers returned by your service using foreach, just return the collection _customerService.GetAllCustomers()?

Categories