StackOverflowException when trying to assign public value to private backing field - c#

I have a public property with private backer like the one below. On the line selected = Selected; I get an SO exception. It would appear that assignment is causing some infinite recursion. Can someone give a more detailed explanation of what is happening? What should the code be instead?
//other class stuff AssetTypes is an enum btw.
private AssetTypes? selected = null;
public AssetTypes? Selected
{
get
{
return selected;
}
set
{
selected = Selected;
if (selected == AssetTypes.Image)
{
image.Click();
}
else if (selected == AssetTypes.Video)
{
video.Click();
}
else
{
selected = null;
}
}
}
The only part that can be changed is the assignment in the setter. The if-else logic needs to be untouched.

A setter has a parameter called value, which is the value that the property is being set to. You should get the new value from that, not from Selected, which just calls the getter, which returns the previous value.
Hence, this line does nothing:
selected = Selected;
And you should never assign to Selected from within its own setter, because that calls the same setter, hence your infinite recursion.
Try this instead:
set
{
if (value == AssetTypes.Image)
{
image.Click();
selected = value;
}
else if (value == AssetTypes.Video)
{
video.Click();
selected = value;
}
else
{
selected = null;
}
}

I think the problem is in the else: Selected = null
Use selected = null with the lowercase s.
Greetings!

You have an infinite recursion on Selected = null, that's why you get a stackoverflow exception. It calls over and over Selected set until it finally blows up with an exception. Change it by
selected = null;

selected = Selected does nothing
what is passed to Set is value;
public AssetTypes? Selected
{
get
{
return selected;
}
set
{
if (selected == value) return; // use this to not do it again
selected = value;
if (selected == AssetTypes.Image)
{
image.Click();
}
else if (selected == AssetTypes.Video)
{
video.Click();
}
else
{
selected = null; // Selected = null; was the recursion
}
NotifyPropertyChanged("Selected"); // this optional and only if you implement INPC
}
}

Related

How to Show text in combobox when no item selected like "..Select room..."

How can I show Text in ComboBox when no item selected in Windows Application using LINQ C#
Here is my code how I get all rooms.... in Combobox.
private void LoadRoom()
{
try
{
db = new HotelEntities();
// cmbProvince.Text = "";
var Room = (from u in db.Room
select new { u.RoomId, u.RoomNumber }).ToList();
cmbRoom.Text = ".. Select.."; // This one do not working.
cmbRoom.DisplayMember = "RoomNumber";
cmbRoom.ValueMember = "RoomId";
cmbRoom.DataSource = Room;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Thank you!
If you set the DataSource of a combobox, a currencymanager is used on the background and its position is selected (the first item).
Instead of setting DataSource, try adding the items:
cmbRoom.Items.AddRange(Room);
NB, setting Text as a placeholder will not work if an item is chosen and cleared later, unless you add an extra check (in TextChanged or SelectedIndexChanged)
Fast solution
Create a source class for your ComboItems, with (at least) the properties to Display and the inner value of the property. If you create a generic class you can use it for all your combo boxes.
class ComboDisplay<TSource>
{
public string Display {get; set;}
public TSource Value {get; set;}
}
cmbRoom.DisplayMember = nameof(ComboDisplay.Display);
cmbRoom.ValueMember = nameof(ComboDisplay.Value);
When you create the data source for your combobox, make sure you add a default value. In the example below I assume that you want to select items of type Room in your combo:
IEnumerable<Room> availableRooms = myDbContext.Rooms
.Where(room => room.IsAvailable)
.Select(room => new ComboDisplay<Room>
{
Display = room.Name,
Value = new Room
{
Id = room.Id,
...
},
})
// add a dummy value if nothing is selected
.Concat(new Room[]
{
Display = "Please select a room",
Value = null, // meaning: nothing selected
});
After selection, use comboBox1.SelectedValue to get the selected Room, or null if nothing is selected.
Create a Special Combobox class
If you have to use this regularly, consider creating a generic sub class of ComboBox that can display items of a certain TSource, and will return null if nothing is selected:
class MyComboBox<TSource> : ComboBox
{
public MyComboBox() : base()
{
base.DataSource = this.EmptyList;
base.DisplayMember = nameof(ComboDisplay.Display);
base.ValueMember = nameof(ComboDisplay.Value);
}
private static readonly EmptyItem = new ComboDisplay
{
Display = "Please select a value",
Value = null,
}
Make a property that returns the available combo items. Make sure that the EmptyItem is always in the collection:
public IReadonlyCollection<TSource> ComboItems
{
get {return (IReadOnlyCollection<TSource>)base.DataSource;}
set
{
// TODO: check if the empty element is in your list; if not add it
base.DataSource = value;
}
}
Finally: the function to get the Selected value, or null if nothing is selected:
public TSource SelectedValue
{
get => return (TSource)base.SelectedValue;
set
{
// TODO: check if value is in ComboItems
base.SelectedValue = value;
}
}

Filter Method of ICollectionView and Properly Binding and ObservableCollection in MVVM

I'm getting stuck into MVVM in WPF and I have setup an ObservableCollection and an ICollectionView. The ICollectionView is set as the ItemsSource of a DataGrid, and the model is a type of Job.
I've setup getters and setter for both of the collections however when I am setting a Filter on the ICollectionView instead of the Job being filtered by the SearchString they're just replicated over and over again, leading me to believe that they way I have the collections setup is totally wrong.
Here is how the two collections are get/set:
public ObservableCollection<Job> AllJobs
{
get
{
foreach (var job in _allJobsList)
_allJobs.Add(job);
return _allJobs;
}
set
{
if (_allJobs == value) return;
OnPropertyChanged("AllJobs");
}
}
public ICollectionView AllJobsView
{
get
{
_allJobsView = CollectionViewSource.GetDefaultView(AllJobs);
return _allJobsView;
}
set
{
if (_allJobsView == value)
{
return;
}
_allJobsView = value;
OnPropertyChanged("AllJobsView");
}
}
Now I have a stringcalled SearchString that is bound to a TextBox.Text. When the text changes I do the following:
public string SearchString
{
get => _searchString;
set
{
if (_searchString == value) return;
_searchString = value;
FilterJobs();
OnPropertyChanged("SearchString");
}
}
private void FilterJobs()
{
AllJobsView.Filter = x =>
{
var viewJob = x as Job;
return viewJob != null && viewJob.Number.Contains(_searchString);
};
}
Now when the page first loads, there are the correct Jobs loaded into the DataGrid. However, as soon as the user types the Jobs are duplicated if the Job.Number does contain the SearchString. How am I able to setup the collections so that I can appropriately set a filter?
The problem is in the getter of your ObservableCollection. Every time you "get" the collection, you are adding every item to the collection all over again.
Your code:
get
{
foreach (var job in _allJobsList)
_allJobs.Add(job);
return _allJobs;
}
Instead, it should be:
get
{
return _allJobs;
}
The setter of your ObservableCollection is also missing the "setter" (private field = value) code:
set
{
if (value != _allJobs)
{
_allJobs = value;
OnPropertyChanged("AllJobs");
}
}
Your Property AllJobs would then be:
private ObservableCollection<Job> _allJobs;
public ObservableCollection<Job> AllJobs
{
get
{
return _allJobs;
}
set
{
if (value != _allJobs)
{
_allJobs = value;
OnPropertyChanged("AllJobs");
}
}
}
The initialization of your collection should be someplace else (and not in the getter of your property), like in the constructor of the ViewModel or/and in a method that a command calls after the user asks for a refresh of the collection.
For example, if your VieModel is called MyViewModel and your List<Job> is called _allJobsList, you can initialize your collection like so:
public MyViewModel()
{
//fill the _allJobsList first, getting from a database for example: _allJobsList = GetJobs();
//and then create an observable collection from that list
AllJobs = new ObservableCollection<Job>(_allJobsList);
}

Update ListcollectionView based on current item of other ListCollectionView

I want to Update ListCollectionView in a listbox each time the Item of another ListCollection gets selected.
I have 2 ListViewCollection, SceneCollectionView and ShotCollectionView. I want to have the SceneCollection filtered based on a property SceneNumber in ShotCollectionView, but I can get the ShotCollectionView to update when I go from one item to the other in SceneCollectionView.
This is my ViewModel
public class ShotListViewModel : NotifyUIBase
{
public ListCollectionView SceneCollectionView { get; set; }
private Scenes CurrentScene
{
get { return SceneCollectionView.CurrentItem as Scenes; }
set { SceneCollectionView.MoveCurrentTo(value); RaisePropertyChanged(); }
}
private ObservableCollection<Shot> _allShots = new ObservableCollection<Shot>();
public ObservableCollection<Shot> AllShots
{
get { return _allShots; }
set { _allShots = value; RaisePropertyChanged();}
}
private ListCollectionView _allShotsCollection;
public ListCollectionView AllShotsCollection
{
get
{
if (_allShotsCollection == null)
{
_allShotsCollection = new ListCollectionView(this.AllShots);
_allShotsCollection.Filter = IsSceneNumber;
}
return _allShotsCollection;
}
}
private bool IsSceneNumber(object obj)
{
if (obj as Shot != null
&& (obj as Shot).SceneNumber == (SceneCollectionView.CurrentItem as Scene).SceneNumber)
{
return true;
}
return false;
}
public ShotListViewModel()
{
SceneCollectionView = Application.Current.Resources["SceneCollectionView"] as ListCollectionView;
GetShotList(); //Populates the AllShots Observable collection.
AddShotCommand = new RelayCommand(AddShot);
FilterShotsCommand = new RelayCommand(AddShot);
}
What am I missing here to make it work or is it better to use ICollectionViewLiveShaping. but I have no idea how to implement that
I don`t understand what you tried to do, but lets talk on an example :
Lets say we have
ListBox1 binded to ListBox1Items and
ListBox2 binded to ListBox2Items.
if you want to filter the data from ListBox2 you have to filter ListBox2Items. How to do that ? Is simple : ListBox1 has a property SelectedItem which you can bind to --- lets say --- ListBox1SelectedItem. Every time when selection change, in the setter of the ListBox1SelectedItem you can trigger a filter on ListBox2Items.
Hope you understand what I`ve explained.

Can't read the value member setting from the data reader to combobox

I'm setting the value member and display member from a datareader to combobox like this.
public void getPartyNamesCombo()
{
SqlDataReader reader = new VotingOP().getPartyNamesToCombo();
while (reader.Read())
{
cmbPartyName.Items.Add(new { PartyID = reader["partyID"].ToString(), PartyName = reader["partyName"].ToString() });
}
cmbPartyName.ValueMember = "PartyID";
cmbPartyName.DisplayMember = "PartyName";
}
I'm trying to access the id like this
int selectedValue = (int)cmbPartyName.SelectedValue;
MessageBox.Show("Selected value is"+selectedValue);
but it gives me "An unhandled exception of type 'System.NullReferenceException'" Exception. What's wrong I'm doing here?
I suggest the following approach:
First: you create some class for your data items:
class MyDataItem
{
public string PartyID { get;set; }
public string PartyName { get;set; }
}
Second: you use it in place of your anonymous object:
public void getPartyNamesCombo()
{
SqlDataReader reader = new VotingOP().getPartyNamesToCombo();
while (reader.Read())
{
cmbPartyName.Items.Add(new MyDataItem() {
PartyID = reader["partyID"].ToString(),
PartyName = reader["partyName"].ToString()
});
}
cmbPartyName.ValueMember = "PartyID";
cmbPartyName.DisplayMember = "PartyName";
}
Third: finally you are now able to cast your selected item to the custom data item and get its properties:
MyDataItem selectedItem = cmbPartyName.SelectedItem as MyDataItem;
if (selectedItem != null)
{
MessageBox.Show(String.Format("You've just selected the '{0}' party with the ID {1}", selectedItem.PartyName, selectedItem.PartyID));
}
Notice here that I used the SelecetedItem which gives you the whole object unlike the SelectedValue where you only get the PartyID.
If you later change your mind and want to show other properties they will already be available.
If there is no selected value currently then the cmbPartyName.SelectedValue will return null. First you need to get the selected value and check if it is not null:
object selectedValue = cmbPartyName.SelectedValue;
if (selectedValue != null)
{
// Now convert the selected value to integer.
int selectedPartyID = (int)selectedValue;
// And now you can handle the integer.
// ...
}
else
{
// There is no value selected...
}

fill individual combobox in gridview combocolumn from another event

I have create two Combobox column in gridview. Now i want to fill second combobox depending on value of first combobox (On first combobox selectedValueChanged event). Please reply.
General approach may look like this:
private MyType1 _selectedItem1;
public MyType1 SelectedItem1
{
get { return _selectedItem1; }
set
{
if (_selectedItem1 == value) return;
_selectedItem1 = value;
//replace with string implementation, if needed
OnPropertyChanged(() => SelectedItem1);
if (_selectedItem1 == ...)
{
ItemsSource2 = new List<MyClass2> { ... };
}
else if (_selectedItem1 == ...)
{
...
}
}
}
private IList<MyType2> _itemsSource2;
public IList<MyType2> ItemsSource2
{
get { return _itemsSource2; }
set
{
if (_itemsSource2 == value) return;
_itemsSource2 = value;
OnPropertyChanged(() => ItemsSource2);
}
}

Categories