I'm new in WPF and C# so go easy on me :)
My goal is to plot data to graph using LiveCharts2 with WPF help and add data live.
I followed the example of LiveCharts2 added the class ViewModel with and the XAML and everything worked fine:
public partial class ViewModel
{
public ISeries[] Series { get; set; } =
{
new LineSeries<double>
{
Values = new double[] { 1, 2 },
Fill = null
}
};
This is static data .. how do I bind it to a variable that changes at any given time? or how should I change the code for that purpose?
I tried to write only XAML code (view code) and took the example from LiveCharts2 to add data to the "Values" but couldn't make it.
I want something like this and just fire and forget & wish that the data plot will update automatically.
new LineSeries<double>
{
Values = new double[] { myChangedata },
Fill = null
}
You need to use an ObservableCollection instead of an array so the chart can receive updates.
public partial class ViewModel
{
private ObservableCollection<double> myChangedData = new();
public ViewModel()
{
Series.Add(new LineSeries<double>
{
Values = myChangedData,
Fill = null,
});
}
public object Sync { get; } = new();
public List<ISeries> Series { get; set; } = new();
}
Then to add data
lock (Sync)
{
// Any changes including adding, clearing, etc must be synced.
myChangedData.Add(1D);
myChangedData.Add(2D);
}
When doing live data be sure you set the SyncContext property on the chart and always lock it before any changes.
<lc:CartesianChart Series="{Binding Series}" SyncContext="{Binding Sync}"/>
Related
We've got a WPF app with a landing page that lists about a dozen or so buttons, all going to new views/viewmodels of that type. Its becoming unwieldy. We've got one viewmodel that lists all of these which basically look like this:
private void ExecuteViewProgramCommand()
{
OpenViewMessage message = new OpenViewMessage();
CurrentViewModel = message.ViewModel = ViewModelLocator.ProgramVM;
Messenger.Default.Send<OpenViewMessage>(message);
}
I've never liked how this was done, as it violates the DRY principle. The only thing that changes in the the above code in the second line, where in this code what changes is ViewModelLocator.ProgramVM. I've been tasked with redoing the landing page, making it more organized and we're going to be adding more launching buttons. I think it would be better to use dependency injection. Also I'm trying to address the need to redesign the display, so that its in a list, rather than buttons scattered about, and in alphabetical order.
First I came up with this class:
public class Tile
{
public string ModuleName { get; set; }
public NamedViewModelBase ModuleViewModel { get; set; }
}
(NamedViewModelBase is the name of the viewmodel that's common to all of the viewmodels.) Then I declared a unit test to test this and declared this within the unit test:
List<Tile> tiles = new List<Tile>()
{
new Tile()
{
ModuleName = "Program",
ModuleViewModel = ViewModelLocator.ProgramVM
},
new Tile()
{
ModuleName = "Organization",
ModuleViewModel = ViewModelLocator.OrganizationVM
}
}
But this quickly became apparent that this was wrong. The assigning in the setter of ViewModelLocator.ProgramVM would instantiate the viewmodel for Program. I don't want that, I'd rather have the calling of instantiating it, such as we have in the ViewModelLocator:
static public ProgramViewModel ProgramVM
{
get
{
if (ServiceLocator.IsLocationProviderSet)
{
SimpleIoc ioc = ServiceLocator.Current as SimpleIoc;
return ioc.GetInstanceWithoutCaching<ProgramViewModel>(Guid.NewGuid().ToString());
}
else
{
return null;
}
}
}
So, I'm thinking that I've got to change the Tile class to declare the ModuleViewModel property to something like this: public NamedViewModelBase ModuleViewModel { get; }. But I don't know how I'd instantiate it when defining a List. What is the correct way to resolve this?
This is going to be psuedo codish advice which is kind of on the same track where you already are:
Assuming BaseViewModel is the base class for all your individual VM's
Create a Dictionary<string, BaseViewModel>
Fill this dictionary up during Application Start time (would look like your tiles List)
public void PreCreateVMs()
{
dictionary[Key] = new ConcreteViewModelType();
// Keep adding New Vms here
}
In the xaml, bind all your buttons to same Command which takes a string argument (or improvise this with Enum). Pass the correct String Key for each button.
Like: Accounts Button click should launch AccountVM which is stored with "AccountVM" key in the dictionary.
In the Command Handler - use the string, lookup the Dictionary find the correct ViewModel and Assign this object to CurrentViewModel
From maintenance point of view - all you need to add a new ViewModel is to update xaml with a new button, assign correct command parameter string. Use this string key and add the correct VM in the PreCreateVMs method.
I've redesigned the Tile class. What I believe I need is for the second parameter to be a Command. I'm asking if this might do better. Here's the new definition of Tile and an example of how I tried to implement it:
public class Tile
{
public string ModuleName { get; set; }
//public NamedViewModelBase ModuleViewModel { get; set; }
public Action ThisCommand { get; set; }
}
And here's how I tried to implement it as a List:
List<Tile> tiles = new List<Tile>()
{
new Tile()
{
ModuleName = "Program",
ThisCommand = () =>
{
if (ServiceLocator.IsLocationProviderSet)
{
SimpleIoc ioc = ServiceLocator.Current as SimpleIoc;
ioc.GetInstanceWithoutCaching<ProgramViewModel>(Guid.NewGuid().ToString());
}
}
},
new Tile()
{
ModuleName = "Organization",
ThisCommand = () =>
{
if (ServiceLocator.IsLocationProviderSet)
{
SimpleIoc ioc = ServiceLocator.Current as SimpleIoc;
ioc.GetInstanceWithoutCaching<OrganizationViewModel>(Guid.NewGuid().ToString());
}
}
}
};
Am I on the right track? Should I define tiles as a Dictionary instead?
I'm trying to make a listview in xamarin show data from a restapi but have the option to filter the list or sort it based upon last name.
I've set the bindingcontext equal to the apiviewmodel which works. But I want to set the itemssource to a list which can be manipulated later instead of the binding context.
Here is the code that works:
Xaml:
<ListView x:Name="DirectoryListView" ItemsSource="{Binding ContactsList}" IsPullToRefreshEnabled="True">
Xaml.cs:
LocalAPIViewModel = new APIViewModel();
BindingContext = LocalAPIViewModel;
APIViewModel.cs:
private List<MainContacts> _ContactsList { get; set; }
public List<MainContacts> ContactsList
{
get
{
return _ContactsList;
}
set
{
if(value != _ContactsList)
{
_ContactsList = value;
NotifyPropertyChanged();
}
}
}
public class MainContacts
{
public int ID { get; set; }
public string FirstName { get; set; }
}
This all works fine. It's only when I add the following lines that it stops displaying the data in the listview:
xaml.cs:
LocalList = LocalAPIViewModel.ContactsList;
DirectoryListView.ItemsSource = LocalList;
I think I need to add these lines so that I can manipulate the list that's being displayed. Why is the list not being displayed? Is this not how it should be done?
According to your description and code, you use MVVM to bind ListView firstly, it works fine, now you want to use Viewmodel to bind ListView itemsource in xaml.cs directly, am I right?
If yes,I do one sample according to your code, that you can take a look, the data can display successfully.
public partial class Page4 : ContentPage
{
public APIViewModel LocalAPIViewModel { get; set; }
public Page4 ()
{
InitializeComponent ();
LocalAPIViewModel = new APIViewModel();
listview1.ItemsSource = LocalAPIViewModel.ContactsList;
}
}
public class APIViewModel
{
public ObservableCollection<MainContacts> ContactsList { get; set; }
public APIViewModel()
{
loadddata();
}
public void loadddata()
{
ContactsList = new ObservableCollection<MainContacts>();
for(int i=0;i<20;i++)
{
MainContacts p = new MainContacts();
p.ID = i;
p.FirstName = "cherry"+i;
ContactsList.Add(p);
}
}
}
public class MainContacts
{
public int ID { get; set; }
public string FirstName { get; set; }
}
so I suggest you can check ContactsList if has data.
Update:
I want to be able to search the list with a search bar and also order it by first or last names. I also want to be able to click on one of the contacts and open up a separate page about that contact
I do one sample that can meet your requirement, you can take a look:
https://github.com/851265601/xf-listview
So, to answer all your questions...
First, the binding.
Once you set the ItemsSource="{Binding ContactsList}" this means that anytime you signal that you have changed your ContactsList by calling OnPropertyChanged(), that is going to be reflected on the ItemsSource property (so, update the UI - that is why we put the OnPropertyChanged() into the setter). Thus, you do not need to manually set the ItemsSource every time you change it. (Especially from the View, as the View should have no knowledge of how the ContactsList is defined in the ViewModel.)
So you can completely remove those lines from the View's code-behind.
Next, the ordering and searching.
What OnPropertyChanged() does, is that it re-requests the bound property from the ViewModel, and updates the View according to that. So, just after OnPropertyChanged() is called, the getter of the bound property (ContactsList) is called by the View.
So, a good idea is to put the sorting mechanism into the getter of the public property. (Or the setter, when resetting the property.) Something like this:
public class ViewModel {
private ObserveableCollection<MainContacts> contactList { get; set; }
public ObserveableCollection<MainContacts> ContactList {
get {
return new ObservableCollection<MainContacts>(contactList
.Where(yourFilteringFunc)
.OrderBy(yourOrderingFunc));
}
set {
contactsList = value;
OnPropertyChanged();
}
}
//...
}
So, whenever your public property is called, it will sort the private property and return the collection that way.
Change public List<MainContacts> ContactsList to public ObservableCollection<MainContacts> ContactsList
in xaml.cs
instead of LocalList = LocalAPIViewModel.ContactsList;, put
ContactsList = new ObservableCollection(LocalAPIViewModel.ContactsList);
I think this will work, instead of setting ListView's Itemsource to 'LocalList'
When populating an observable collection, I can see that the "return" is not being called when I "set" the new data in the collection. It does work if I set the data from a different location in the program so I must be not understanding some nuance of the way it works. The part that works is when I take out the commented code under "This works", "ChooseFile()" does not. In the debugger I can see the OptionsToChoose has data in both cases. When it works the XAML is updated correctly.
class ScripterViewModel : BindableBase
{
public ScripterViewModel()
{
ScripterModel scripterModel = new ScripterModel();
ObservableCollection<string> tabsChoice = new ObservableCollection<string>();
tabsChoice.Add("Tabs");
tabsChoice.Add("Buttons");
Tabs = tabsChoice;
this.OpenFileBtn = new DelegateCommand(chooseFile, canChooseFile).ObservesProperty(() => OpenFile);
this.SaveFileBtn = new DelegateCommand(saveFile, canSaveFile).ObservesProperty(() => SaveFile);
//This works
//var myJSONDoc = JsonConvert.DeserializeObject<JSONclass>(File.ReadAllText(#"C:\Users\mike\Documents\Haas\Scripter\settings.json"));
//OptionsToChoose = new ObservableCollection<Tabbed>(myJSONDoc.TabbedBtns);
}
public void chooseFile()
{
var myJSONDoc = JsonConvert.DeserializeObject<JSONclass>(File.ReadAllText(#"C:\Users\mike\Documents\Haas\Scripter\settings.json"));
OptionsToChoose = new ObservableCollection<Tabbed>(myJSONDoc.TabbedBtns);
}
public ObservableCollection<Tabbed> _optionsToChoose = new ObservableCollection<Tabbed>();
public ObservableCollection<Tabbed> OptionsToChoose
{
get
{
return _optionsToChoose;
}
set
{
_optionsToChoose = value;
}
}
}
When you are creating the OptionsToChoose in the constructor it will be initialized when the viewmodel is used by the view.
In the example that is not working, you are just replacing the ObservableCollection with a new one instead clearing it and adding the items. Therefore you need to notify that the property has been changed like V.Leon pointed out in his answer.
Or just clear the existing collection and populate it with the values from the json.
var myJSONDoc = JsonConvert.DeserializeObject<JSONclass>(File.ReadAllText(#"C:\Users\mike\Documents\Haas\Scripter\settings.json"));
OptionsToChoose.Clear();
foreach (var item in myJSONDoc.TabbedBtns)
{
OptionsToChoose.Add(item);
}
You are not raising PropertyChanged event in the setter of OptionsToChoose. You already extend BindableBase, so raising PropertyChanged event can be done by replacing your current OptionsToChoose property implementation with the following:
public ObservableCollection<Tabbed> OptionsToChoose
{
get
{
return _optionsToChoose;
}
set
{
SetProperty(ref _optionsToChoose, value);
}
}
See BindableBase.SetProperty Method
Ideally, you should not change the whole reference of ObservableCollection after it is binded. Instead clear items in it and then add new items in it.
public ObservableCollection<Tabbed> _optionsToChoose = new ObservableCollection<Tabbed>();
public ObservableCollection<Tabbed> OptionsToChoose
{
get
{
return _optionsToChoose;
}
}
OptionsToChoose.Clear();
OptionsToChoose.Add(foo);
As has already been brought up, given your code you would need to make the property for your collection raise PropertyChanged if you were resetting the collection. That said ObservableCollection is really not an ideal collection type to use. What I would recommend is including MvvmHelpers in your project and using the ObservableRangeCollection
public class MyPageViewModel : BindableBase
{
public MyPageViewModel()
{
OptionsToChoose = new ObservableRangeCollection<Tabbed>();
SomeCommand = new DelegateCommand(OnSomeCommandExecuted);
}
public DelegateCommand SomeCommand { get; }
public ObservableRangeCollection<Tabbed> OptionsToChoose { get; }
private void OnSomeCommandExecuted()
{
// get some updated data
IEnumerable<Tabbed> foo = DoFoo();
OptionsToChoose.ReplaceRange(foo);
}
}
You get a couple of benefits there. One you're not allocating and deallocating your collection. Also the ObservableRangeCollection updates the full list before raising PropertyChanged or CollectionChanged events this results in few UI notifications and better app performance.
I have an unusual problem with my view model. I have a list of the items and I need to to have a button with attached command to each item. I'm using ItemsSource and each item is represented with this view model:
public class CarItemViewModel : ViewModelBase, ICarItemViewModel
{
public void Init(Car definition, Action<Car> onSelection)
{
Wehicle = definition;
SelectCarCommand = new RelayCommand(() => onSelection(definition));
}
public Car Wehicle { get; private set; }
public ICommand SelectCarCommand { get; private set; }
}
Then in my ViewModel for page I'm calling method below to populate list in OnNavigatedTo or Loaded event:
public void ShowCars()
{
var newCar = new Car()
{
Make = "Mazda",
Model = "MX-5"
};
var carVM = new CarItemViewModel();
carVM.Init(newCar, SelectCar);
Cars.Add(carVM);
}
Binding for data is working fine. I can see names etc but button with bound command is sometimes inactive and it won't hit a break point in SelectCar method. When I do a little trick and before calling ShowCars() I add Task.Delay(200) it will be fine.
I'm developing for Windows Phone 8 Silverlight and using newest MVVM Light. Anyone got similar issue?
How can I add or delete items from my DataContext? This is my code:
class WallModel
{
public WallModel()
{
WallItems = new ObservableCollection<Wall>();
Initialization = InitializeAsync();
}
public Task Initialization { get; private set; }
public async Task InitializeAsync()
{
WallItems.Add(new Wall { id = 2, user = 3 });
}
public ObservableCollection<Wall> WallItems { get; set; }
}
And MainPage.xaml.cs:
public MainPage()
{
this.InitializeComponent();
DataContext = new WallModel();
lvMain.DataContext = DataContext;
}
We don't generally add or remove items from a DataContext directly. Instead, (in MVVM) we try to create a class that incorporates all of the properties that we want to display in the UI and methods that perform the required functionality. Then we set an instance of this class as the DataContext.
Of course, you can just set a simple collection property as the DataContext of one control and in that case, you could just add or remove items from that collection as normal. However, it is generally preferred to manipulate the data item(s) set as the DataContext rather than the DataContext object itself.
You can use for example:
((WallModel)DataContext).WallItems.Remove(item);
or
((WallModel)DataContext).WallItems.RemoveAt(index);
....
Also if lvMain is in the MainPage you do not need to set its datacontext because it gets inherited.
As Sheridan mentions use a viewmodel and a Delete command which removes the item directly in the viewmodel.
((WallModel)DataContext).WallItems.Add(new Wall { id = 2, user = 3 });