I am using the FastWPFGrid control found here:
https://github.com/FormatD/FastWpfGrid
I have looked at the sample applications and while there is a lot there to help with the implementation of the actual grid, I am at a bit of a loss as to how to get the binding to work. I am trying to bind the model of the fastgrid to a property of my datacontext - GridViewModel - this in turn is a view model which inherits from the fastgrid viewmodelbase.
So far, so good. The problem is that when the data changes, the notification isn't happening. See my simple example below. In this example, when I change the number of rows nothing happens. If I manually refresh the xaml (by changing the name of the binding to an invalid one and then back) it then updates. I just need to know how to trigger the notifypropertychanged from code.
I would appreciate a very simple example along the lines of the below:
My Xaml:
<fastWpfGrid:FastGridControl Grid.Row="1" Grid.Column="0" Model="{Binding SummaryVm}"/>
The main viewmodel (the datacontext of my form)
public SummaryGridViewModel SummaryVm{ get; set; }
// This Event fires when I know that the row count has changed (for example)
void OnRunListPropertyChanged(Message.RunListPropertyChanged obj)
{
// **This is where I need help to get the view to update
}
The Grid View Model
public class SummaryGridViewModel : FastGridModelBase
{
public SummaryGridViewModel(RunListCalculationQueueManager runList)
{
RunList = runList;
}
RunListCalculationQueueManager RunList { get; }
public override int ColumnCount => 15;
public override int RowCount =>
RunList.ActiveRun == null ? 0 : RunList.BaselineRun == null ? 1 : 3;
}
So the event in the main viewModel fires as expected, but no change is reflected in the view.
I am not necessarily looking for this code to be fixed, but a simple working example of what I am trying to do would be great.
Found the solution, it was pretty simple really -
FastGridModelBase exposes several notify methods. Calling
SummaryVM.NotifyRefresh
caused the notification to fire and everything works well.
Related
I am creating a simple WPF app that when you click on a button it runs through a few steps like copy the file to a new location, convert the file then it copies the new file back to the original location.
The steps are working fine but I would like to have the WPF window update to which step it is on and hide the button while it is running.
The window only updates once it has finished running my code. I think I used to be able to do this on classic forms with me.refresh but this doesn't work on WPF.
Is something I can do to update the window after each step is complete?
Thank you
Button1.Visibility = Windows.Visibility.Hidden
FileCopy("C:\Test.xsf", AppPath & "\Convert\test.xsf")
Image7.Visibility = Windows.Visibility.Hidden
Image3.Visibility = Windows.Visibility.Visible
Program.StartInfo.FileName = xDefs
Program.StartInfo.Arguments = "/q"
Program.Start()
Program.WaitForExit()
Image5.Visibility = Windows.Visibility.Visible
FileCopy("AppPath & "\Convert\test.csv, "C:\Test.csv")
Button1.Visibility = Windows.Visibility.Visible
In order to update the UI while your program is busy, you'll need to use the Dispatcher class to add your update request onto the UI message queue. Take this synchronous example:
public void DoWorkWithFile(string filePath)
{
CopyFile(filePath);
ConvertFile(filePath);
CopyFileBack();
}
We could use the Dispatcher class to break this up and feed messages back to the UI in between tasks:
public void DoWorkWithFile(string filePath)
{
CopyFile(filePath);
RunOnUiThread((Action)delegate { SomeUiTextBlock.Text = "Copied" });
ConvertFile(filePath);
RunOnUiThread((Action)delegate { SomeUiTextBlock.Text = "Converted" });
CopyFileBack();
RunOnUiThread((Action)delegate { SomeUiTextBlock.Text = "Copied back" });
}
private object RunOnUiThread(Action method)
{
return Dispatcher.Invoke(DispatcherPriority.Normal, method);
}
I know this is a VB.NET tagged question but I'll just go ahead and share a C# solution. I hope you know enough of it to port it to VB. This is the first time and posting anything to stackoverflow, if it solves your problem please mark it as the answer :-)
You must first know a thing or two (actually a lot more) on data binding. You basically create a view model, define the property that changes with time and bind this to the window. In this case you must define a value to keep track of the current operation and let the button control.
Disclaimer, I wrote this in notepad and haven't tested it on visual studio. Be on the lookout for typos.
using System.ComponentModel;
namespace FileConverter
{
//define the various states the application will transition to
public enum OperationStatus
{
CopyingFileToNewLocation
ConvertingFile,
CopyingFileToOriginalLocation
OperationCompelete
}
//Defines the view model that shall be bound to the window.
//The view model updates the UI using event notifications. Any control that had enabled
//binding will get updated automatically
public class ViewModel : INotifyPropertyChanged//This interface defines an event used to raise an event and notify subscribers of a changed in data
{
private OperationStatus _FileConvertionStatus;
public event PropertyChangedEventHandler PropertyChanged;
public OperationStatus FileConvertionStatus
{
get
{
return _FileConvertionStatus;
}
set
{
_FileConvertionStatus=value;
//Notify all UIElements / objects that had subscribed to this property that it has changed
RaisePropertyChanged(this,"FileConvertionStatus");
}
}
public void RaisePropertyChanged(object sender,string propertyName)
{
//check if there is any object that had subscribed to changes of any of the data properties in the view model
if(PropertyChanged!=null)
PropertyChanged(sender,new PropertyChangedEventArgs(propertyName));
}
public void StartFileConvertion(string filePath)
{
//Any time we change the property 'FileConvertionStatus', an event is raised which updates the UI
this.FileConvertionStatus=OperationStatus.CopyingFileToNewLocation;
StartCopyingToNewLocation(); //call your copying logic
this.FileConvertionStatus=OperationStatus.ConvertingFile;
StartFileConvertion(); //call your conversion logic
this.FileConvertionStatus=OperationStatus.CopyingFileToOriginalLocation();
CopyFileToOriginalLocation(); //...
this.FileConvertionStatus=OperationStatus.OperationCompelete;
}
}
}
//Now for the UI section
In the constructor of the window, you must bind the window to the view model right after this window has been initialized
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel vm=new ViewModel();
//setting the data context property the window implicitly binds the whole window to our view model object
this.DataContext=vm;
string filePath="c:\file.txt";
//start the file manipulation process
vm.StartFileConvertion(filePath);
}
}
//Next step we need to bind the button to the 'FileConvertionStatus' property located in the view model. We don't bind the button to the whole view model, just the property that it's interested in. Having bound the window to the view model in the previous code, all child elements get access to the public properties of this view model (VM from now on). We do the property binding in XAML
..Button x:Name="btnStartFileProcessing" Enabled="{Binding FileConvertionStatus}"...
We're almost there. One this is missing. You'll notice that the 'Enabled' property is a Boolean value. The 'FileConvertionStatus' property is enum. Same way you can't assign an enum to a Boolean directly, you need to do some convertion. This is where converters come in.
Converters allow you to define how one property can be converted to a different one in XAML. In this case we want the button to be enabled only when file conversion is successful. Please do some reading into this.
Create a class as shown below:
using System.Windows.Data;
namespace FileConverter
{
public class OperationStatusToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType,object parameter,System.Globalization.CultureInfo culture)
{
OperationStatus status=(OperationStatus)value;
switch(status)
{
case OperationStatus.OperationCompelete:
return true; //enable the button when everything has been done
default:
return false;//disable the button as the file processing is underway
}
}
public object ConvertBack(object value, Type targetType,object parameter,System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Next step is to define the converter in XAML code. Think of this as initializing it though it can't be further from the true :-). Its more of importing the namespace into the xaml.Put the code below in the App.XAML file. Doing such declaration in the App.XAML file makes the code visible globally.
xmlns:MyConverters="clr-namespace:FileConverter"
In the Application.Resources XAML tag, declare the converter as shown below
<Application.Resources>
<MyConverters:OperationStatusToBooleanConverter x:Key="OperationStatusToBooleanConverter"/>
</Application.Resources>
Final Step
Redo the binding code in the button to include the converter.
...Button Enabled="{Binding FileConvertionStatus,Converter={StaticResource OperationStatusToBooleanConverter}}" x:Name="btnStartFileProcessing" ...
Please note that I haven't thread-optimized this code, the main problem is that all work is being done on the UI thread which can lead to the window hanging if an operation takes long.
The amount of work needed to properly set the binding up as per MVVM code standards is a lot. It might seem like an over-kill and at times, it actually is. Keep this in mind though, once the UI gets complex MVVM will definitely save the day due to the separation of concerns and binding strategies.
I asked a similar question a while back here WPF MVVM User Control. I got some answers, but they were way off, so I guess I wasn't clear in what I want to do....
I am working on a WPF application using MVVM. The app is built using a component based approach, so I have some user controls that I've defined that will be used throughout the application. As an example, I have an Address control. I want to use it an multiple places throughout the application. Here's an example. See here:
http://sdrv.ms/1aD775H
The part with the green border is the Address control. The control has its own View Model.
When I place it on a window or other control, I need to tell it the PK of the customer to load addresses for. So I created a Customer ID DependencyProperty:
public partial class AddressView : UserControl
{
public AddressView()
{
InitializeComponent();
}
public static DependencyProperty CustomerIdProperty = DependencyProperty.Register("CustomerId", typeof(int), typeof(AddressView),
new UIPropertyMetadata(0, AddressView.CustomerIdPropertyChangedCallback, AddressView.CustomerIdCoerce, true));
public int CustomerId
{
// THESE NEVER FIRE
get { return (int)GetValue(CustomerIdProperty); }
set { SetValue(CustomerIdProperty, value); }
}
private static void CustomerIdPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
// THIS NEVER FIRES
AddressView instance = (AddressView)d;
instance.CustomerId = (int)args.NewValue;
}
enter code here
private static object CustomerIdCoerce(DependencyObject d, object value)
{
return value; // <=== THIS FIRES ON STARTUP
}
}
Then in the MainWindowView I have:
<vw:AddressView Grid.Row="1"
Grid.Column="0"
x:Name="AddressList"
CustomerId="{Binding ElementName=TheMainWindow, Path=SelectedCustomer.Id, Mode=TwoWay}"/>
Note my comments in the user control's CS. The Coerce fires on startup. The callback never fires, nor do the CustomerId getter or setter.
What I would like to happen seems simple, I just can't make it work....
When a customer is selected the CustomerId should be passed to the Address UserControl. Then in the VM for the Address UserControl should handle getting & saving the data.
So, again, 2 questions:
1) Anyone see what's wrong?
2) How does the UserControl DP send the PK to the ViewModel?
If anyone's interested, my sample project is here: http://sdrv.ms/136bj91
Thanks
Your CustomerId getter and setter will never fire in this situation. They are there simply as helper methods in case you want to access the CustomerIdProperty property from your code behind.
Your CustomerIdPropertyChangedCallback method will not fire because your binding expression is incorrect. You need to bind to the DataContext of MainWindow and not the window itself:
...
CustomerId="{Binding ElementName=TheMainWindow, Path=DataContext.SelectedCustomer.Id}"
...
Also, make sure that you are calling the INotifyPropertyChanged PropertyChanged event when the property bound to the ComboBox is changed.
Try this :
CustomerId="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.YourSelectedItem.TheProperty}"
I am not sure how to manage your seleted item in window, so please change yourSelectedItem.TheProperty accordingly.
I've got the following question: what's the expected scenario for the logic when I would want to bind some elements inside of ViewModel separatly. What I mean...
http://slodge.blogspot.co.uk/2013/04/n3-kitten-cells-on-iphone-n1-days-of.html
There is a "Kitten" class in the sample provided - this is just a common "DTO" object.
And there is also a modelview class which contains those objects list:
public List<Kitten> Kittens
{
get ...
set { ... RaisePropertyChanged(() => Kittens); }
}
We can bind a grid with cells (which bound to Kitten properties). But what if I would want to be able to activate RaisePropertyChanged on every property of Kitten separatly? I.e.,
if the kitten Title changed, then to call RaisePropertyChanged (and accordingly, change only bound cell value instead of the whole list refreshing) on KittenTitle property (for instance)?
The sample with Kittens is obviously primitive and does not need such implementation, but what if instead of Kittens I would have a list similar to Facebook App menu panel, where there are menu items (amount of which can vary) and those items can have "Notifications Count" label (or can not) so, instead of the complete refresh of the list, how can I initiate that label only refreshing (caused by related property inside the "Kitten" instance changed)?
(That looks like viewModel inside viewModel for me, but not sure how to solve it smarter with MvvmCross).
Thank you!
You can implement Nested INotifyPropertyChanged objects - exactly as you do in Windows binding.
So if one Kitten raises its property changed then only that part of the UI for that kitten will refresh
e.g. a Kitten could be written:
public class DynamicKitten : MvxNotifyPropertyChanged // can use MvxViewModel as base class if preferred
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; RaisePropertyChanged(() => Name); }
}
}
For some examples of this - mostly using Linq to wrap static objects - see:
https://github.com/slodge/MvvmCross-Tutorials/tree/master/InternetMinute
https://github.com/slodge/MvvmCross-Tutorials/tree/master/MonoTouchCellTutorial (discussed pre-v3 in http://slodge.blogspot.co.uk/2013/01/uitableviewcell-using-xib-editor.html)
https://github.com/slodge/MvvmCross-Tutorials/tree/master/Sample%20-%20CirriousConference
One of my favorite StackOverflow libraries took this INPC approach all the way back to the Json layer - take a look at all the INPC entities in https://stacky.codeplex.com/SourceControl/latest#trunk/source/Stacky/Entities/Answer.cs
I'm working on my first project in WPF/XAML, and there's a lot I've not figured out.
My problem is simple - I need a window that has a bunch of fields at the top, with which the user will enter his selection criteria, a retrieve button, and a data grid. When the user clicks on the button, a query is run, and the results are used to populate the grid.
Now the simple and obvious and wrong way to implement this is to have a single module containing a single window, and have everything contained within it - entry fields, data grid, the works. That kind of mangling of responsibilities makes for an unmaintainable mess.
So what I have is a window that is responsible for little more than layout, that contains two user controls - a criteria control that contains the entry fields and the retrieve button, and a data display control that contains the data grid.
The question is how to get the two talking to each other.
Years back, I would have added a function pointer to the criteria control. The window would have set it to point to a function in the display control, and when the button was clicked, it would have called into the display control, passing the selection criteria.
More recently, I would have added an event to the criteria control. I would have had the window set a handler in the display control to listen to the event, and when the button was clicked, it would have raised the event.
Both of these mechanisms would work, in WPF. But neither is very XAMLish. It looks to me like WPF has provided the ICommand interface specifically to accommodate these kinds of connection issues, but I've not yet really figured out how they are intended to work. And none of the examples I've seen seem to fit my simple scenario.
Can anyone give me some advice on how to fit ICommand to this problem? Or direct me to a decent explanation online?
Thanks!
MVVM is the prevalent pattern used with WPF and Silverlight development. You should have a read up on it.
Essentially, you would have a view model that exposes a command to perform the search. That same view model would also expose properties for each of your criteria fields. The view(s) would then bind to the various properties on the view model:
<TextBox Text="{Binding NameCriteria}"/>
...
<Button Command="{Binding SearchCommand}".../>
...
<DataGrid ItemsSource="{Binding Results}"/>
Where your view model would look something like:
public class MyViewModel : ViewModel
{
private readonly ICommand searchCommand;
private string nameCriteria;
public MyViewModel()
{
this.searchCommand = new DelegateCommand(this.OnSearch, this.CanSearch);
}
public ICommand SearchCommand
{
get { return this.searchCommand; }
}
public string NameCriteria
{
get { return this.nameCriteria; }
set
{
if (this.nameCriteria != value)
{
this.nameCriteria = value;
this.OnPropertyChanged(() => this.NameCriteria);
}
}
}
private void OnSearch()
{
// search logic, do in background with BackgroundWorker or TPL, then set Results property when done (omitted for brevity)
}
private bool CanSearch()
{
// whatever pre-conditions to searching you want here
return !string.IsEmpty(this.NameCriteria);
}
}
MVVM pattern is implemented in my Silverlight4 application.
Originally, I worked with ObservableCollection of objects in my ViewModel:
public class SquadViewModel : ViewModelBase<ISquadModel>
{
public SquadViewModel(...) : base(...)
{
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
...
_model.DataReceivedEvent += _model_DataReceivedEvent;
_model.RequestData(...);
}
private void _model_DataReceivedEvent(ObservableCollection<TeamPlayerData> allReadyPlayers, ...)
{
foreach (TeamPlayerData tpd in allReadyPlayers)
{
SquadPlayerViewModel sp = new SquadPlayerViewModel(...);
SquadPlayers.Add(sp);
}
}
...
}
Here is a peacie of XAML code for grid displaying:
xmlns:DataControls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data"
...
<DataControls:DataGrid ItemsSource="{Binding SquadPlayers}">
...</DataControls:DataGrid>
and my ViewModel is bound to DataContext property of the view.
This collection (SquadPlayers) is not changed after its creation so I would like to change its type to
List<SquadPlayerViewModel>
. When I did that, I also added
RaisePropertyChanged("SquadPlayers")
in the end of '_model_DataReceivedEvent' method (to notify the grid that list data are changed.
The problem is that on initial displaying grid doesn't show any record... Only when I click on any column header it will do 'sorting' and display all items from the list...
Question1: Why datagrid doesn't contain items initially?
Q2: How to make them displayed automatically?
Thanks.
P.S. Here is a declaration of the new List object in my view-model:
public List<SquadPlayerViewModel> SquadPlayers { get; set; }
You can't use List as a binding source, because List not implement INotifyCollectionChanged it is require for WPF/Silverlight to have knowledge for whether the content of collection is change or not. WPF/Sivlerlight than can take further action.
I don't know why you need List<> on your view model, but If for abstraction reason you can use IList<> instead. but make sure you put instance of ObservableCollection<> on it, not the List<>. No matter what Type you used in your ViewModel Binding Only care about runtime type.
so your code should like this:
//Your declaration
public IList<SquadPlayerViewModel> SquadPlayers { get; set; }
//in your implementation for WPF/Silverlight you should do
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
//but for other reason (for non WPF binding) you can do
SquadPlayers = new List<SquadPlayerViewModel>();
I usually used this approach to abstract my "Proxied" Domain Model that returned by NHibernate.
You'll need to have your SquadPlayers List defined something like this:
private ObservableCollection<SquadPlayerViewModel> _SquadPlayers;
public ObservableCollection<SquadPlayerViewModel> SquadPlayers
{
get
{
return _SquadPlayers;
}
set
{
if (_SquadPlayers== value)
{
return;
}
_SquadPlayers= value;
// Update bindings, no broadcast
RaisePropertyChanged("SquadPlayers");
}
}
The problem is that whilst the PropertyChanged event informs the binding of a "change" the value hasn't actually changed, the collection object is still the same object. Some controls save themselves some percieved unnecessary work if they believe the value hasn't really changed.
Try creating a new instance of the ObservableCollection and assigning to the property. In that case the currently assigned object will differ from the new one you create when data is available.