I'm developing WPF applications using MVVM pattern. I have ViewModel with code like this:
public bool EditModeEnabled
{
get { return _EditModeEnabled; }
set
{
_ModeEditModeEnabled = value;
OnPropertyChanged("EditModeEnabled");
OnPropertyChanged("CommentTextBoxVisibility");
}
}
OnPropertyChanged is virtual method of base class which just raise PropertyChanged event.
I want to test PropertyChanged event raising and there my test method:
public void EditModeEnabledTest()
{
var imageViewModel = TestHelper.GetTestImageViewModel();
var firedEvents = new List<string>();
imageViewModel.PropertyChanged += ((sender, e) => firedEvents.Add(e.PropertyName));
imageViewModel.Mode = true;
Assert.AreEqual(firedEvents.Count, 2);
Assert.IsTrue(firedEvents.Contains("EditModeEnabled"));
Assert.IsTrue(firedEvents.Contains("CommentTextBoxVisibility"));
...
}
Is it a good way to test ProprtyChanged event?
I use a little Fluent API for doing exactly that. It allows you to write tests like this:
var imageViewModel = TestHelper.GetTestImageViewModel();
imageViewModel.ShouldNotifyOn(s => s.EditModeEnabled)
When(s => s.Mode = true);
Besides being succinct, I prefer this approach because it's type-safe - no string values to keep in sync with your API.
To test that the event is being raised for more than one property, you can just write another test that does this. This will give you many tests, but each will be very small and you avoid Assertion Roulette.
I believe it’s a good idea to unit test the PropertyChanged event in the example you have shown. You might have written the property name string wrong which would result in a missing update.
With the WPF Application Framework (WAF) it’s very easy to write such a unit test:
Person person = new Person();
AssertHelper.PropertyChangedEvent(person, x => x.Name, () => person.Name = "Luke");
Similar to the suggestion of Mark Seemann, with Fluent Assertions you have simple nice methods that wrap it all up for you, like:
subject.Should().Raise().PropertyChangeFor(x => x.SomeProperty);
Source
https://fluentassertions.com/eventmonitoring/
Related
I find that I often like to create and add a new item to a list when populating lists in a loop:
foreach(var cat in ctx.InventoryCategories)
{
pnl_catList.Controls.Add(new RadioButton()
{
Text = cat.CategoryName,
Tag = cat,
Checked = false,
// how could I do this?
Click += onClick(),
})
}
But the only way I know now on how to add the event listener is the long way:
foreach(var cat in ctx.InventoryCategories)
{
var newButton = new RadioButton()
{
Text = cat.CategoryName,
Tag = cat,
Checked = false,
})
newButton.Click += onClick();
pnl_catList.Controls.Add(newButton);
}
Is this bad practice or is there a good short way to bind the events?
A field assignment and adding a member to an event are two different things. The C# language allows the use of = as an object initialization operator, but not += or -=, which are implemented as add and remove functions behind the scenes in the event class. It was a language choice - I believe it is based on the desire NOT to include complex functionality in the constructor to keep the initialization phase clean.
Getting around this restriction is possible, but no solution is easier to understand, and requires fewer lines of code than simply adding a listener after the object is initialized.
In short, there's no shorthand.
I have a .Net 4.5 app that is moving to WPF-based RxUI (kept up to date, 6.0.3 as of this writing). I have a text field that should function as a filter field with the fairly common throttle etc. stuff that was part of the reason for going reactive in the first place.
Here is the relevant part of my class.
public class PacketListViewModel : ReactiveObject
{
private readonly ReactiveList<PacketViewModel> _packets;
private PacketViewModel _selectedPacket;
private readonly ICollectionView _packetView;
private string _filterText;
/// <summary>
/// Gets the collection of packets represented by this object
/// </summary>
public ICollectionView Packets
{
get
{
if (_packets.Count == 0)
RebuildPacketCollection();
return _packetView;
}
}
public string FilterText
{
get { return _filterText; }
set { this.RaiseAndSetIfChanged(ref _filterText, value); }
}
public PacketViewModel SelectedPacket
{
get { return _selectedPacket; }
set { this.RaiseAndSetIfChanged(ref _selectedPacket, value); }
}
public PacketListViewModel(IEnumerable<FileViewModel> files)
{
_packets = new ReactiveList<PacketViewModel>();
_packetView = CollectionViewSource.GetDefaultView(_packets);
_packetView.Filter = PacketFilter;
_filterText = String.Empty;
this.WhenAnyValue(x => x.FilterText)
.Throttle(TimeSpan.FromMilliseconds(300)/*, RxApp.TaskpoolScheduler*/)
.DistinctUntilChanged()
.ObserveOnDispatcher()
.Subscribe(_ => _packetView.Refresh());
}
private bool PacketFilter(object item)
{
// Filter logic
}
private void RebuildPacketCollection()
{
// Rebuild packet list from data source
_packetView.Refresh();
}
}
I unit test this using Xunit.net with Resharper's test runner. I create some test data and run this test:
[Fact]
public void FilterText_WhenThrottleTimeoutHasPassed_FiltersProperly()
{
new TestScheduler().With(s =>
{
// Arrange
var fvm = GetLoadedFileViewModel();
var sut = new PacketListViewModel(fvm);
var lazy = sut.Packets;
// Act
sut.FilterText = "Call";
s.AdvanceToMs(301);
// Assert
var res = sut.Packets.OfType<PacketViewModel>().ToList();
sut.Packets.OfType<PacketViewModel>()
.Count().Should().Be(1, "only a single packet should match the filter");
});
}
I put a debug statement on the Subscribe action for my FilterText config in the constructor of the class, and it gets called once for each packet item at startup, but it never gets called after I change the FilterText property.
Btw, the constructor for the test class contains the following statement to make threading magic work:
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
My problem is basically that the Refresh() method on my view never gets called after I change the FilterText, and I can't see why not.
Is this a simple problem with my code? Or is this a problem with a CollectionViewSource thing running in a unit testing context rather than in a WPF context?
Should I abandon this idea and rather have a ReactiveList property that I filter manually whenever a text change is triggered?
Note: This works in the application - the FilterText triggers the update there. It just doesn't happen in the unit test, which makes me wonder whether I am doing it wrong.
EDIT: As requested, here are the relevant bits of XAML - this is for now just a simple window with a textbox and a datagrid.
The TextBox:
<TextBox Name="FilterTextBox"
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding FilterText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
The datagrid:
<DataGrid ItemsSource="{Binding Path=Packets}"
Name="PacketDataGrid"
SelectedItem="{Binding SelectedPacket}"
AutoGenerateColumns="False"
EnableRowVirtualization="True"
SelectionMode="Single"
SelectionUnit="FullRow"
CanUserAddRows="False"
CanUserResizeRows="False"
>
<DataGrid.Columns>
...
If anything else is relevant/needed, let me know!
EDIT 2: Paul Betts recommends not doing the SynchronizationContext setup in the test constructor like I do, probably for very valid reasons. However, I do this because of the way another viewmodel (FileViewModel) works - it needs to wait for a MessageBus message to know that packet processing is complete. This is something that I am working actively on trying to avoid - I know the MessageBus is a very convenient bad idea. :) But this is the cause for the SyncContext stuff. The method that creates the test viewmodel looks like this:
private FileViewModel GetLoadedFileViewModel()
{
var mre = new ManualResetEventSlim();
var fvm = new FileViewModel(new MockDataLoader());
MessageBus.Current
.Listen<FileUpdatedPacketListMessage>(fvm.MessageToken.ToString())
.Subscribe(msg => mre.Set());
fvm.LoadFile("irrelevant.log");
mre.Wait(500);
return fvm;
}
I realize this is bad design, so please don't yell. ;) But I am taking a lot of legacy code here and moving it into RxUI based MVVM - I can't do it all and end up with a perfect design just yet, which is why I am getting unit tests in place for all this stuff so that I can do Rambo refactoring later. :)
Btw, the constructor for the test class contains the following statement to make threading magic work:
Don't do this
My problem is basically that the Refresh() method on my view never gets called after I change the FilterText, and I can't see why not.
I believe your problem is the commented out part:
.Throttle(TimeSpan.FromMilliseconds(300)/, RxApp.TaskpoolScheduler/)
And this part:
.ObserveOnDispatcher()
When you use TestScheduler, you must use RxApp.[MainThread/Taskpool]Scheduler for all scheduler parameters. Here above, you're using a real TaskpoolScheduler and a real Dispatcher. Since they're not under TestScheduler, they can't be controlled by TestScheduler.
Instead, write:
this.WhenAnyValue(x => x.FilterText)
.Throttle(TimeSpan.FromMilliseconds(300), RxApp.TaskpoolScheduler)
.DistinctUntilChanged()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => _packetView.Refresh());
and everything should work.
Is there any way to get custom binding behavior in .net win forms?
Example, I'm connecting my control to a BindingSource object and adding a binding like
this.slider.DataBindings.Add(new System.Windows.Forms.Binding("Enabled", this.bindingSourceModel, "FloatProperty > 0.5f", true));
There's no way the above will work but I like it to be enabled if dataSource.FloatProperty becomes greater than 0.5f.
Is there any way to do this?
I understand what you want to do, so I've slightly modified your situation for the sake of demonstration: the UI setup is obvious, there is a TrackBar and a Button, and the problem here is to bind the Enabled property of button to the boolean value of the expression trackBar.Value > 50.
The idea is to turn the main form into something like a ViewModel (as in MVVM). Observe that I am implementing INotifyPropertyChanged.
public partial class ManiacalBindingForm : Form, INotifyPropertyChanged {
public ManiacalBindingForm() {
InitializeComponent();
this.button.DataBindings.Add("Enabled", this, "ManiacalThreshold", true, DataSourceUpdateMode.OnPropertyChanged);
this.trackBar.ValueChanged += (s, e) => {
this.Text = string.Format("ManiacalBindingForm: {0}", this.trackBar.Value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ManiacalThreshold"));
};
}
public bool ManiacalThreshold {
get { return this.trackBar.Value > 50; }
}
public event PropertyChangedEventHandler PropertyChanged;
...
}
Now, this is my personal observation: While there is a non-trivial interpretation of your goal, your goal is a bit maniacal. You have to ponder why exactly you want to achieve this through data-binding. Binding is mostly aimed for automatic, bi-directional, sync'ing of property values. Doing this type of UI update via binding directly to the "model" is even more maniacal. But you got credit for being maniacal, though! ;-)
In the code behind of my Silverlight application, I have the need to re-populate the TreeView and then make a specific TreeViewItem selected.
The code itself is pretty simple, here it is (i'll trim and pseudo-code-ify it to make it as short as possible)
private void Button_Click()
{
Guid idToSelect = TellMeWhatToSelect();
List<myObject> myDataList = myObjectRepository.RetrieveData().ToList();
myTreeView.Items.Clear();
foreach(myObject o in myDataList)
{
myTreeView.Items.Add(new TreeViewItem() { Content = o.DataField, Tag = o.Id });
}
myTreeView.Items.First(o => ((Guid)(o as TreeViewItem).Tag).Equals(idToSelect)).IsSelected = true;
}
That's basically it: i'm reading some data into myDataList, then i cycle through it and create as many TreeViewItems as needed in order to display the data.
Problem is, myTreeView.SelectedItem is null at the end of this, and SelectionChanged event isn't triggered. I would think that, since Items collection has been cleared and re-filled, switching IsSelected on one of the items would act like clicking, but it seems it doesn't).
Oddly enough (for me at least), issuing myTreeView.Items.First().IsSelected = true; by itself (that is, calling a method with that single line of code inside) works as expected: SelectedItem is there and all events are fired appropriateyl.
What's wrong with my code and/or what am I missing ? Looks like cleaning items up kind of breaks something.
I'm fairly sure others have had similar issues, but a bunch of searches I tried didn't help (most of the info & questions I came up with are WPF-related).
Thanks for your time, I'll provide more info if needed. Also, sorry for the wall of text.
UPDATE
Modifying code like this, now the method works as expected.
private void Button_Click()
{
Guid idToSelect = TellMeWhatToSelect();
List<myObject> myDataList = myObjectRepository.RetrieveData().ToList();
myTreeView.Items.Clear();
foreach(myObject o in myDataList)
{
myTreeView.Items.Add(new TreeViewItem() { Content = o.DataField, Tag = o.Id });
}
Dispatcher.BeginInvoke(()=>
{
myTreeView.Items.First(o => ((Guid)(o as TreeViewItem).Tag).Equals(idToSelect)).IsSelected = true;
});
}
Set property IsSelected inside of Dispatcher.BeginInvoke.
I had the same problem a while ago, I've solved by calling the UpdateLayout method from the treeview before setting the TreeViewItem as selected.Like this:
myTreeView.UpdateLayout();
myTreeView.Items.First(o => ((Guid)(o as TreeViewItem).Tag).Equals(idToSelect)).IsSelected = true;
I'm trying to display the content of a table in a combobox.
I'm using the MVVM pattern and in my viewmodel class if I write this it works:
private IEnumerable<EventType> _eventTypes;
public ManageProfileModel()
{
_referenceData = new ReferenceDataContext();
_referenceData.Load(_referenceData.GetEventTypesQuery(), false);
_eventTypes = _referenceData.EventTypes;
}
Like this the combobox is displaying the data.
However, I want the _eventTypes to be a List:
private List<EventType> _eventTypes;
But if I write this:
public ManageProfileModel()
{
_referenceData = new ReferenceDataContext();
_referenceData.Load(_referenceData.GetEventTypesQuery(), false);
_eventTypes = _referenceData.EventTypes.ToList();
}
then the combobox is empty. What is wrong with that?
I want to use a List, because I want to be able to add and remove data in the list.
Best regards.
If I remember correctly, you can not convert IEnumerable to IList directly. It is little tricky. I would use of the options from the following link. I have it in bookmark since I ran into the same problem.
http://devlicio.us/blogs/derik_whittaker/archive/2008/03/28/simple-way-to-convert-ienumerable-lt-entity-gt-to-list-lt-ientity-gt.aspx
or look at this link
http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/af225aa0-1cf4-40dd-ac3e-e7a19edaef00
DomainContext.Load is asynchronous, so in your second example you're creating a list that's most likely empty because the EntitySet hasn't finished loading yet. Use the code posted by StackOverflowException to defer creating the list until the EntitySet has been populated and it should work.
just a shot straight from the head...
did you try to add something like propertychanged event for your list?
so it could be that the data came async and the property was not informed about the change...
like I said ...
private List<EventType> _eventTypes;
public List<EventType> EventTypes
{
get { return _eventTypes; }
set
{
_eventTypes = value;
RaisePropertyChanged("EventTypes");
}
}
and take also a look at ObservableCollections...
Like I said just a shot...
Hope this helps
I don't have much MVVM exposure but with silverlight + RIA, I usually do something like this.
private List<EventType> _eventTypes;
public ManageProfileModel()
{
_referenceData = new ReferenceDataContext();
var op = _referenceData.Load(_referenceData.GetEventTypesQuery(), false);
op.Completed += op_Completed;
}
void po_Completed(object sender, EventArgs e)
{
var op = ( InvokeOperation<IEnumerable<EventType>>)sender;
_eventTypes = op.Value.ToList();
}