I am trying to migrate a small prototype application I made in WinForms to WPF. I'm having some issues with a combobox in WPF not changing values when I select a different value from the drop-down. Initially, I tried just copying the code that I used in my WinForms app to populate the combobox and determine if a new index had been selected. This is how my WinForms code looked like:
private void cmbDeviceList_SelectedIndexChanged(object sender, EventArgs e)
{
var cmb = (Combobox) sender;
var selectedDevice = cmb.SelectedItem;
var count = cmbDeviceList.Items.Count;
// find all available capture devices and add to drop down
for(var i =0; i<count; i++)
{
if(_deviceList[i].FriendlyName == selectedDevice.ToString())
{
_captureCtrl.VideoDevices[i].Selected = true;
break;
}
}
}
Earlier in the code, I am populating the _deviceList List and the combo box (in Form1_Load to be specific) by looping over the available devices and adding them. I tried the same approach in WPF and could only populate the combo box. When I selected a new value, for some reason the same exact value (the initial device) was being sent into the event code (cmbCaptureDevices_SelectionChanged in my WPF app). I looked around for some tutorials in WPF and found that maybe data binding was my issue, and I tried that out instead. This is my combobox in my XAML file:
<ComboBox ItemsSource="{Binding Devices}" Name="cmbCaptureDevices"
IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding CurrentDevice,
Mode=TwoWay}" Se;ectionChanged="cmbCapturedDevices_SelectionChanged" />
There's more to that XAML definition, but it's all arbitrary stuff like HorizontalAlignment and whatnot. My VideoDevicesViewModel inherits from INotifyPropertyChanged, has a private List<Device> _devices and a private Device _currentDevice. The constructor looks like:
public VideoDevicesViewModel()
{
_devices = GetCaptureDevices();
DevicesCollection = new CollectionView(_devices);
}
GetCaptureDevices simply is the loop that I had in my WinForms app which populates the list with all avaialble capture devices on the current machine. I have a public CollectionView DevicesCollection { get; private set; } for getting/setting the devices at the start of the application. The property for my current device looks like:
public Device CurrentDevice
{
get { return _currentDevice; }
set
{
if (_currentDevice = value)
{
return;
}
_currentDevice = value;
OnPropertyChanged("CurrentDevice");
}
}
OnPropertyChanged just raises the event PropertyChanged if the event isn't null. I'm new to WPF (and pretty new to C# in general, honestly) so I'm not sure if I'm missing something elementary or not. Any idea as to why this combobox won't change values for me?
Discovered the answer on my own here. The unexpected behavior was a result of using the Leadtools Device class. It's a COM component and apparently was not playing nicely with my application. I honestly don't understand why exactly it worked, but I wrapped the Device class in another class and used that instead. As soon as I was using the wrapper class, the combo box functioned as it should.
You are using the assignment operator '=' instead of the equality operator '=='
Change
if (_currentDevice = value)
to
if (_currentDevice == value)
Try the following
if _currentDevice == value ...
Related
I have a combobox bound to an Enum. When I click on a new value in my combobox, I'm generating a message to an attached device and getting a response back. I want to file whatever value is received back.
My regular-old bound property looks like this:
private Enum enumValue;
public Enum EnumValue
{
get => enumValue;
set
{
if (enumValue != value)
{
enumValue = value;
sendToDevice();
}
}
}
My update from device looks like this:
public void SetValueFromDevice(string valueFromDevice)
{
enumValue = (Enum)Enum.Parse(EnumType, valueFromDevice);
RaisePropertyChanged(nameof(EnumValue));
}
Note that I'm setting the private variable enumValue directly because I don't want to trigger another outgoing message to my device if I don't need to.
Here's the situation: Let's say I have an enum that looks like this:
public enum Sources
{
Off,
Low,
Mid,
Hi
}
The firmware I've written doesn't like the value of low on certain versions on the device. Instead, it'll reply back with whatever the current value is. I want to file that back to the combo box instead of showing the incorrect value to the user.
Now, what seems to happen is after I click on the combobox item, it sends a message to my device, gets the response, and then files it back appropriately. I can step through and see the combobox doing the "get" after I call RaisePropertyChanged, but it doesn't seem to be updating the selected item at all.
I can call SetValueFromDevice and that always works, so it seems like it's the UI acting up.
I even attempted to totally force it with
control.DropDownClosed += new EventHandler(SourceBox_DropDownClosed);
....
private void SourceBox_DropDownClosed(object sender, EventArgs e)
{
ComboBox box = sender as ComboBox;
box.GetBindingExpression(ComboBox.SelectedItemProperty).UpdateTarget();
}
...and that did nothing.
What am I doing wrong here? Is there any way to set the item of the combobox during the item selection interaction?
Figured it out: I needed to make my sendToDevice() call asyncronous, because I'd be triggering the RaisePropertyChanged twice in the same process, and it'd only "see" the first one.
App.Current.Dispatcher.InvokeAsync(() => sendToDevice());
I am making a Universal Windows App, which includes inserting text into a textbox. I want my app to suggest text from a file to insert to the textbox. But I could not find that property. I have added the textbox in the MainPage.xaml through XAML tags. I believe there is a property for this operation in WPF API. I am just not sure if I can do this in UWP.
I recommend using the AutoSuggestBox control for UWP. The auto-suggest results list populates automatically once the user starts to enter text. The results list can appear above or below the text entry box.
<AutoSuggestBox PlaceholderText="Search" QueryIcon="Find" Width="200"
TextChanged="AutoSuggestBox_TextChanged"
QuerySubmitted="AutoSuggestBox_QuerySubmitted"
SuggestionChosen="AutoSuggestBox_SuggestionChosen"/>
private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
// Only get results when it was a user typing,
// otherwise assume the value got filled in by TextMemberPath
// or the handler for SuggestionChosen.
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{
//Set the ItemsSource to be your filtered dataset
//sender.ItemsSource = dataset;
}
}
private void AutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
{
// Set sender.Text. You can use args.SelectedItem to build your text string.
}
private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
if (args.ChosenSuggestion != null)
{
// User selected an item from the suggestion list, take an action on it here.
}
else
{
// Use args.QueryText to determine what to do.
}
}
Here is the link to the GitHub repo for a complete UI basics sample.
Hope this helps.
This may not apply for UAP but with WPF there's a trick that allows a "dropdown suggestion list". You can replace text box with a combobox and populate it's items when user types. This can be achieved by doing bindings like so:
Text={ Binding Path=meCurrentValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged }
ItemsSource={Binding Path=meFilteredListOfSuggestions, Mode=TwoWay }
Then within your viewmodel you can simply do:
public string meCurrentValue
{
get { return _mecurrentvalue; }
set {
_mecurrentvalue = value;
updateSuggestionsList();
NotifyPropertyChanged("meCurrentValue");
NotifyPropertyChanged("meFilteredListOfSuggestions"); // notify that the list was updated
ComboBox.Open(); // use to open the combobox list
}
public List<string> meFilteredListOfSuggestions
{
get{return SuggestionsList.Select( e => e.text.StartsWith(_mecurrentvalue));}
}
EDIT:
Remember to set the editable value of the combobox to TRUE, this way it will act like a normal textbox.
Is this documentation still valid or am I missing something?
http://doc.xceedsoft.com/products/XceedWpfToolkit/Xceed.Wpf.Toolkit~Xceed.Wpf.Toolkit.PropertyGrid.PropertyGrid~SelectedObjects.html
PropertyGrid control does not appear to have SelectedObjects or SelectedObjectsOverride members. I'm using the latest version (2.5) of the Toolkit against .NET Framework 4.0.
UPDATE
#faztp12's answer got me through. For anyone else looking for a solution, follow these steps:
Bind your PropertyGrid's SelectedObject property to the first selected item. Something like this:
<xctk:PropertyGrid PropertyValueChanged="PG_PropertyValueChanged" SelectedObject="{Binding SelectedObjects[0]}" />
Listen to PropertyValueChanged event of the PropertyGrid and use the following code to update property value to all selected objects.
private void PG_PropertyValueChanged(object sender, PropertyGrid.PropertyValueChangedEventArgs e)
{
var changedProperty = (PropertyItem)e.OriginalSource;
foreach (var x in SelectedObjects) {
//make sure that x supports this property
var ProperProperty = x.GetType().GetProperty(changedProperty.PropertyDescriptor.Name);
if (ProperProperty != null) {
//fetch property descriptor from the actual declaring type, otherwise setter
//will throw exception (happens when u have parent/child classes)
var DeclaredProperty = ProperProperty.DeclaringType.GetProperty(changedProperty.PropertyDescriptor.Name);
DeclaredProperty.SetValue(x, e.NewValue);
}
}
}
Hope this helps someone down the road.
What i did when i had similar problem was I subscribed to PropertyValueChanged and had a List filled myself with the SelectedObjects.
I checked if the contents of the List where of the same type, and then if it is so, I changed the property in each of those item :
PropertyItem changedProperty = (PropertyItem)e.OriginalSource;
PropertyInfo t = typeof(myClass).GetProperty(changedProperty.PropertyDescriptor.Name);
if (t != null)
{
foreach (myClass x in SelectedItems)
t.SetValue(x, e.NewValue);
}
I used this because i needed to make a Layout Designer and this enabled me change multiple item's property together :)
Hope it helped :)
Ref Xceed Docs
I have a piece of code doesn't work properly. If I execute the btnNew once there is no problem. If I execute twice I get a error of...
Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.
Main class
ClassA obj = new ClassA();
private void btnNew_Click(object sender, RoutedEventArgs e)
{
//List strings for clearing and then creating new strings for Title combobox
ObservableCollection<string> calledList = obj.GetList();
cbTitle.Items.Clear();
cbTitle.ItemsSource = calledList;
}
ClassA.cs
private ObservableCollection<string> data = new ObservableCollection<string>();
public ObservableCollection<string> GetList()
{
return data;
}
public void SimpleNew()
{
data.Add("A");
data.Add("B");
}
if I use a if statement in the main class it will eliminate the problem then it will create duplicate strings in the combobox. Then I am asking myself do I need to create a method to handle distinct? I am not sure on this.
This my if statement in the main class
if (cbTitle.Items.Count == 0)
{
ObservableCollection<string> calledList = obj.GetList();
cbTitle.Items.Clear();
cbTitle.ItemsSource = calledList;
}
When I used try/catch it catches error and shows the message. So this is not good either.
So my question is can anyone tell me how to solve this problem?
You cannot set both the ItemsSource property and the Items property together. Try simply removing your calls to cbTitle.Items.Clear() which is unnecessary if you are setting the ItemsSource property on the next line anyway.
UPDATE >>>
You only need to set the ItemsSource property once, preferably in XAML:
<ComboBox ItemsSource="{Binding Items}" ... />
Once this is done, you shouldn't set it again. To change the items in the ComboBox, simply change the items in the collection... they are now data bound... this is WPF, not WinForms:
private void btnNew_Click(object sender, RoutedEventArgs e)
{
//List strings for clearing and then creating new strings for Title combobox
ObservableCollection<string> calledList = obj.GetList();
Items = calledList;
}
Thanks Sheridan. I have also discovered that if I do...
ObservableCollection<string> calledList = obj.GetList();
calledList.Clear(); // I have to use this line of code
calledList.ItemsSource = calledList;
This solve my problem. I am not using xaml because it gave me problems. You may remember I opened a thread about combobox when navigating through records. I managed to solve that problem by using for loop. have a look at my other thread, if you wish, here
However this is not the final solution. I am learning wpf and its cud operation so it will be interesting what I will discover
I've created a control derived from ComboBox, and wish to unit test its behaviour.
However, it appears to be behaving differently in my unit test to how it behaves in the real application.
In the real application, the Combobox.DataSource property and the .Items sync up - in other words when I change the Combobox.DataSource the .Items list immediately and automatically updates to show an item for each element of the DataSource.
In my test, I construct a ComboBox, assign a datasource to it, but the .Items list doesn't get updated at all, remaining at 0 items. Thus, when I try to update the .SelectedIndex to 0 in the test to select the first item, I recieve an ArgumentOutOfRangeException.
Is this because I don't have an Application.Run in my unit test starting an event loop, or is this a bit of a red herring?
EDIT: More detail on the first test:
[SetUp]
public void SetUp()
{
mECB = new EnhancedComboBox();
mECB.FormattingEnabled = true;
mECB.Location = new System.Drawing.Point( 45, 4 );
mECB.Name = "cboFind";
mECB.Size = new System.Drawing.Size( 121, 21 );
mECB.TabIndex = 3;
mECB.AddObserver( this );
mTestItems = new List<TestItem>();
mTestItems.Add( new TestItem() { Value = "Billy" } );
mTestItems.Add( new TestItem() { Value = "Bob" } );
mTestItems.Add( new TestItem() { Value = "Blues" } );
mECB.DataSource = mTestItems;
mECB.Reset();
mObservedValue = null;
}
[Test]
public void Test01_UpdateObserver()
{
mECB.SelectedIndex = 0;
Assert.AreEqual( "Billy", mObservedValue.Value );
}
The test fails on the first line, when trying to set the SelectedIndex to 0. On debugging, this appears to be because when the .DataSource is changed, the .Items collection is not updated to reflect this. However, on debugging the real application, the .Items collection is always updated when the .DataSource changes.
Surely I don't have to actually render the ComboBox in the test, I don't even have any drawing surfaces set up to render on to! Maybe the only answer I need is "How do I make the ComboBox update in the same way as when it is drawn, in a unit test scenario where I don't actually need to draw the box?"
Since you're simply calling the constructor, a lot of functionality of the combobox will not work. For example, the items will be filled when the ComboBox is drawn on screen, on a form. This does not happen when constructing it in a unit test.
Why do you want to write a unit test on that combobox?
Can't you seperate the logic which now is in the custom control? For example put this in a controller, and test that?
Why don't you test on the DataSource property instead of the Items collection?
I'm sure that Application.Run absence cannot affects any control's behavior
I'm having the same problem with a combo box where the items are data bound. My current solution is to create a Form in the test, add the combo box to the Controls collection, and then show the form in my test. Kind of ugly. All my combo box really does is list a bunch of TimeSpan objects, sorted, and with custom formatting of the TimeSpan values. It also has special behavior on keypress events. I tried extracting all the data and logic to a separate class but couldn't figure it out. There probably is a better solution but what I'm doing seems satisfactory.
To make testing easier, I created these classes in my test code:
class TestCombo : DurationComboBox {
public void SimulateKeyUp(Keys keys) { base.OnKeyUp(new KeyEventArgs(keys)); }
public DataView DataView { get { return DataSource as DataView; } }
public IEnumerable<DataRowView> Rows() { return (DataView as IEnumerable).Cast<DataRowView>(); }
public IEnumerable<int> Minutes() { return Rows().Select(row => (int)row["Minutes"]); }
}
class Target {
public TestCombo Combo { get; private set; }
public Form Form { get; private set; }
public Target() {
Combo = new TestCombo();
Form = new Form();
Form.Controls.Add(Combo);
Form.Show();
}
}
Here is a sample test:
[TestMethod()]
public void ConstructorCreatesEmptyList() {
Target t = new Target();
Assert.AreEqual<int>(0, t.Combo.DataView.Count);
Assert.AreEqual<int>(-1, t.Combo.SelectedMinutes);
Assert.IsNull(t.Combo.SelectedItem);
}
This solve some problems if target is ComboBox or any other control:
target.CreateControl();
but I was unable to set SelectedValue it has null value, my test working with two data sources for combo box, one as data source and second is binded to selevted value. With other controls everithing working fine. In the begining I was also creating form in tests, but there is problem when form on created on our build server while tests are executed.
I did a little hack to allow this in my custom derived combobox:
public class EnhancedComboBox : ComboBox
{
[... the implementation]
public void DoRefreshItems()
{
SetItemsCore(DataSource as IList);
}
}
The SetItemsCore function instructs the base combobox to load internal items with the provided list, it's what uses internally after the datasource changes.
This function never gets called when the control is not on a form, because there are lots of checks for CurrencyManagers and BindingContexts that are failing because this components, I believe, are provided by the parent form somehow.
Anyway, in the test, you have to call mECB.DoRefreshItems() just after the mECB.DataSource = mTestItems and everything should be fine if you only depend on the SelectedIndex and the Items property. Any other behavior like databinding is probably still not functional.