How to access ComboBox selected item in code behind - combobox

I would like to access the SelectedItem property for a ComboBox.
In WPF, the properties can be called by Name.Property (i.e. ComboBoxName.SelectedItem). I want to know what item is currently selected in the ComboBox, which is why I need to do this, but it is telling me the name doesn't exist in the current context. I have a SelectionChanged event handler that I want to update a string with the current selected item when it is changed.
.xaml
<ComboBox Name="Generation" Items="{Binding Generation}" SelectedIndex="0" SelectionChanged="Gen_SelectionChanged"/>
.xaml.cs
private void Gen_SelectionChanged(object sender, SelectionChangedEventArgs args)
{
var SelectedItem = Generation.SelectedItem;
}
The error I recieve when doing this is for the Generation in Generation.SelectedItem, telling me "The name "Generation" does not exist in the current context"

Generated C# fields aren't supported yet. You can use this.FindControl<ComboBox>("Generation") after loading XAML instead.

Related

Object reference not set to an instance of an object when creating two ComboboxItems functions in wpf

I´m all out of ideas here
The thing is that Im using two comboboxes and I want to get values from both comboboxes to show content in DataGrid in wpf.
I have this function that gets values from both comboboxes. This works well.
private void cboxYearChange(object sender, SelectionChangedEventArgs e)
{
ComboBoxItem typeItemYear = (ComboBoxItem)comboBox2.SelectedItem;
string valueYear = typeItemYear.Content.ToString();
ComboBoxItem typeItemMonth = (ComboBoxItem)comboBox1.SelectedItem;
string valueMonth = typeItemMonth.Content.ToString();
}
But then I want to create another function to check for changes on the other combobox:
private void cboxMonthChange(object sender, SelectionChangedEventArgs e)
{
ComboBoxItem typeItemYear = (ComboBoxItem)comboBox2.SelectedItem;
string valueYear = typeItemYear.Content.ToString();
ComboBoxItem typeItemMonth = (ComboBoxItem)comboBox1.SelectedItem;
string valueMonth = typeItemMonth.Content.ToString();
}
I can build, but when I run this I get the Object reference not set to an instance of an object error on the ComboBoxItem typeItemYear = (ComboBoxItem)comboBox2.SelectedItem; line in the cboxMonthChange function
What am I missing here ?
SelectedItem is null until something is selected. Unless they both change at the same time (which is not possible as these events are fired in sequence), either the type cast on comboBox1.SelectedItem or comboBox2.SelectedItem will throw an exception.
Check if SelectedItem is set the methods.
Or use another cast, like:
ComboBoxItem item1 = comboBox1.SelectedItem as ComboBoxItem;
if (item1 != null)
{
// do something
}
Hope this helps :-)
1) you should not refer to control's name within the code whenever possible.
So you can know, for instance, which ComboBox was changed within a SelectionChanged
handler by casting the Sender to a ComboBox.
2) but in such a simple case, just use public properties and bind them to
your ComboBox : all will get done with no code.
<ComboBox x:Name="YearSelectCB" SelectedItem="{Binding SelectedYear}">
<ComboBox x:Name="MonthSelectCB" SelectedItem="{Binding SelectedMonth}">
(you can set the DataContext of the window in several ways, for instance in the
window loaded event handler (DataContext=this) )

MVVM WPF databound checkbox won't firing events to the ViewModel for Checked and Unchecked states

I have a databound CheckBox inside a DataGrid, using WPF and MVVM;
<DataGridTemplateColumn Width="80" Header="Enabled">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsEnabled, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Name="theCheckbox" HorizontalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
This works fine and the CheckBox is being checked when IsEnabled is set. IsEnabled is a property in my collection of objects which I have bound to the DataGrid. What I want to do is to be able to validate if a specific rows CheckBox within the DataGrid should be allowed to be checked when the user selects it and if not remove their check and display a warning message something like "Row 1 cannot be checked without rows 5 and 9 being checked". I found out how to do this using code behind using the Checked and Unchecked properties of the CheckBox, but I am using MVVM and therefore want to handle things in the ViewModel associated with the View the DataGrid and CheckBox are in. How do I do this? I need a way of passing the Id field of the DataRow through too in order to identify which row I am working on, for arguments sake lets say the Id field is called BorderId.
Implement IDataErrorInfo on your objects, which is WPF's default interface for validation, and setup your validation code to validate if the checkbox can be checked or not
This is actually a bit trickier than normal because you are validating a property on your data item using data that doesn't exist on the data item, which means you need to provide some way of attaching unrelated validation to your object at run time.
The solution I usually use is to expose a ValidationDelegate from my object, which other objects can use to attach additional validation to the object. The code for it is posted on my blog, and it allows you to attach validation to your object like this:
public class MyViewModel
{
// Keeping this generic to reduce code here, but it
// should be a full property with PropertyChange notification
public ObservableCollection<SomeObject> SomeCollection { get; set; }
public MyViewModel()
{
SomeCollection = LoadDataGridObjects();
// Add the validation delegate to each object
foreach(var item in SomeCollection)
item.AddValidationDelegate(ValidateObject);
}
// Validation Delegate to verify the right checkboxes are checked
private string ValidateObject(object sender, string propertyName)
{
if (propertyName == "IsChecked")
{
var item = (SomeObject)sender;
if (item.Id == 1
&& !SomeCollection.First(p => p.Id == 5).IsChecked
&& !SomeCollection.First(p => p.Id == 9).IsChecked)
{
return "Row 1 cannot be checked without rows 5 and 9 being checked";
}
}
return null;
}
}
Could you add to the PropertyChanged event handler of each item in your list? Something like this:
foreach (Object x in List)
{
x.PropertyChanged += OnItemChanged;
}
Then in the change listener you can do whatever you like with the sender object including changing the IsEnabled property.
void OnItemChanged(object sender, PropertyChangedEventArgs e)
{
// change sender object properties or set bound label text here
}
If I understand your question, you have a checkbox and you want to control if it can be checked or not. Instead of letting the user check it at all times and push back an error message if you don't want to allow it, make your checkbox inactive in the first place if you don't want the user to check it.
To do this through your ViewModel (which is of course your DataContext), you need to bind the Checkbox's "Command" to a custom command in your ViewModel.
A WPF Command will provide the following functionality:
Automatically enable/disable a control based on custom logic
Handles the "action" event of your control (on a button of checkbox, it would be "click").

Set controls on wpf form to readonly or editable

I am writing a data entry application and am having problems dealing with add vs edit vs view 'modes' of the app.
The scenario: From a window, the user selects items in two combo boxes and then clicks a button. If the combo box selections don't match an existing record, the form should open with a new record ready to have values inserted. If the selections DO match an existing record, this record should be opened with all the textboxes in the form in 'Readonly' mode. Should the user wish to modify the existing data, they click the form's Edit button which sets the textboxes as 'Editable'.
I'm new to wpf, but in my vba apps, I'd normally have a DataMode property and a ToggleControls method which would loop through the TabControl's TabItems and then loop through the TabItems' controls, setting the IsReadOnly property of any TextBox controls found based on the 'DataMode' property. However, I have doubts about whether this is the path to follow in wpf.
So, my question is, do i use the approach above? If so, how do I access the controls in a TabItem. This doesn't work: foreach (Control ctrl in MyTabCtrl) { //if Textbox do stuff }.
If this isn't the way to do it, can someone show me the way? I'm guessing it's either a template / style issue or something to do with setting the data as readonly somehow, and then binding the TextBox's IsReadOnly property to the state of the data. Edit: Oh, or that ViewStateManager thing. All of which i know little about.
An easy approach would be
In Code behind declare a DependencyProperty named IsReadOnly
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(
"IsReadOnly",
typeof (bool),
typeof (MainWindow)
);
public bool IsReadOnly
{
get { return (bool) GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
Implement the Buttons Click event, so that IsReadOnly gets false on Click
private void Button_Click(object sender, RoutedEventArgs e)
{
IsReadOnly = false;
}
Bind the TextBoxes to the IsReadonly property using DataBinding
<TextBox IsReadOnly="{Binding Path=IsReadOnly, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Mode=OneWay}" />
This binding assumes that your code behind is not your DataContext and you therefore have to bind relatively to the Window/ UserControl which the CodeBehind is applied to.
This way the TextBoxes listen to changes on this property. When it switches, they change their ReadOnly state automatically.
You can use DataContext :
Bind the datacontext of your tabControl to your record (with the button event click write
this.Canvas.DataContext = _yourRecord for sample)
public void bntEvent_Click(object sender, EventArgs e)
{
// Do matching and put the result in result field
MyTabCtrl.DataContext = false;
}
Bind IsReadOnly/IsEnable property of your textboxs on your datacontext and use a converter to return true/false value
<TextBox IsReadOnly="{Binding .}" />
See http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx for more informations about Converters.
I hope I help you and you understand my bad English.
Good bye

How to pass the selectedItem of a listbox to the View Model

This is a running question that I have updated to hopefully be a little more clear.
In short what I am trying to accomplish is pass a property from a listbox selected item to the viewmodel so that this property can be used within a new query. In the code below the Listbox inherits databinding from the parent object. The listbox contains data templates (user controls) used to render out detailed results.
The issue I am having is that within the user control I have an expander which when clicked calls a command from the ViewModel. From what I can see the Listbox object is loosing it's data context so in order for the command to be called when the expander is expanded I have to explicitly set the datacontext of the expander. Doing this seems to instantiate a new view model which resets my bound property (SelectedItemsID) to null.
Is there a way to pass the selected item from the view to the viewmodel and prevent the value from being reset to null when a button calls a command from within the templated listbox item?
I realize that both Prism and MVVMLite have workarounds for this but I am not familiar with either framework so I don't know the level of complexity in cutting either of these into my project.
Can this be accomplished outside of Prism or MVVMLite?
original post follows:
Within my project I have a listbox usercontrol which contains a custom data template.
<ListBox x:Name="ResultListBox"
HorizontalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding SearchResults[0].Results,
Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionChanged="ResultListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<dts:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch">
<!-- CFS Template -->
<dts:TypeTemplateSelector.CFSTemplate>
<DataTemplate>
<qr:srchCFS />
</DataTemplate>
</dts:TypeTemplateSelector.CFSTemplate>
<!-- Person Template -->
<dts:TypeTemplateSelector.PersonTemplate>
<DataTemplate>
<qr:srchPerson />
</DataTemplate>
</dts:TypeTemplateSelector.PersonTemplate>
<!-- removed for brevity -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
SelectionChanged calls the following method from the code behind
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);//binds to VM
}
Within the ViewModel I have the following property
public string SelectedItemID
{
get
{
return this._SelectedItemID;
}
set
{
if (this._SelectedItemID == value)
return;
this._SelectedItemID = value;
}
}
the listbox template contains a custom layout with an expander control. The expander control is used to display more details related to the selected item. These details (collection) are created by making a new call to my proxy. To do this with an expander control I used the Expressions InvokeCommandAction
<toolkit:Expander Height="auto"
Margin="0,0,-2,0"
Foreground="#FFFFC21C"
Header="View Details"
IsExpanded="False"
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
Style="{StaticResource DetailExpander}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<i:InvokeCommandAction Command="{Binding GetCfsResultCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Within the ViewModel the delegate command GetCFSResultCommandExecute which is called is fairly straight forward
private void GetCfsResultCommandExecute(object parameter)
{
long IdResult;
if (long.TryParse(SelectedItemID, out IdResult))
{
this.CallForServiceResults = this._DataModel.GetCFSResults(IdResult);}
The issue I am experiencing is when selecting a listbox Item the selectionchanged event fires and the property SelectedItemID is updated with the correct id from the selected item. When I click on the expander the Command is fired but the property SelectedItemID is set to null. I have traced this with Silverlight-Spy and the events are consistent with what you would expect when the expander is clicked the listbox item loses focus, the expander (toggle) gets focus and there is a LeftMouseDownEvent but I cannot see anything happening that explains why the property is being set to null. I added the same code used in the selection changed event to a LostFocus event on the listboxt item and still received the same result.
I'd appreciate any help with understanding why the public property SelectedItemID is being set to null when the expander button which is part of the listbox control is being set to null. And of course I would REALLY appreciate any help in learning how prevent the property from being set to null and retaining the bound ID.
Update
I have attempted to remove the datacontext reference from the Expander as this was suggested to be the issue. From what I have since this is a data template item it "steps" out of the visual tree and looses reference to the datacontext of the control which is inherited from the parent object. If I attempt to set the datacontext in code for the control all bindings to properties are lost.
My next attempt was to set the datacontext for the expander control within the constructor as
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
This approach does not seem to work as InvokeCommandAction is never fired. This command only seems to trigger if data context is set on the expander.
thanks in advance
With this line you create a new SearchViewModelDataSource using its default constructor.
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
I guess this is why you find null because this is the default value for reference type.
You can resolve the issue by setting DataContext to the same instance used to the main controll (you can do it by code after all components are initialized).
Hope this help!
Edit
I don't think that binding may be lost after setting datacontext from code. I do it every time I need to share something between two or more model.
In relation to the code you've written :
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
Instead of using this.cfsExpander you can try to use the FindName method. Maybe this will return you the correct instance.
object item = this.FindName("expander_name");
if ((item!=null)&&(item is Expander))
{
Expander exp = item as Expander;
exp.DataContext = this._ViewModel;
}
Try if its work for you.
Of course, this._ViewModel has to expose a property of type ICommand named GetCfsResultCommand but I think this has been already done.
While this was a hacky approach I found an intermediate solution to get the listbox item value to the view model. I ended up using the selection changed event and passing the value directly to a public property wihtin my view model. Not the best approach but it resolved the issue short term
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
MySelectedValue = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);
}
For this to fire I did have to also setup a property changed handler within the view to push the change to the VM. You can disregard the MySelectedValue line as it is secondary code I have in place for testing.
For those intereted the generic property changed handler
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}

Mainaining radio selection after item source change

Setup:
I have a combo-box, it's itemsource bound to an ObservableCollection<T> of a custom class, one property is a List<myenum>.
I have an itemscontrol which is databound to the combo-box's selected item List<myenum> property.
The itemscontrol datatemplate creates a list of radiobuttons, each representing the individual enum values in the list.
The Desire:
When I change the value in the combo-box the itemscontrol source is updated. What I want to occur, is if a radio button in the new itemscontrol source is the same as the selected radiobuton in the previous list (before it was updated), this to be checked.
Current Idea:
Asign a Checked event to the radio buttons, which maintains a myenum property in the window class which can be compared against. Make the IsChecked property of the radiobox bind to a converter and compare against the myenum property. To achieve this, I have made the window class extend from IValueConverter, this way the converter function has access to the myenum property.
Issue:
I don't know how to get the IsChecked binding to use the window as the converter. I have tried using relative source in the converter part of the binding, but that doesn't work
IsChecked="{Binding Converter={RelativeSource={RelativeSource Self}}}"
Preferred Answers:
Assistance on correcting the binding syntax if it's possible this way.
Ideas of a more appropriate way of achieving what I'd like.
I also do not know how to use the window as a value converter in xaml. Instead create a standalone value converter class with a public property for the enum type. Next, in the constructor of the window, get a reference to the instance of the value converter and store it in a private member.
XAML:
<local:MyValueConverter x:Key="ConvertSomething" />
Code behind:
private MyValueConverter _myValueConverter;
public Window1()
{
InitializeComponent();
_myValueConverter = FindResource("ConvertSomething") as MyValueConverter;
}
private void RadioButton_Checked(object sender, RoutedEventArgs e)
{
// You can access _myValueConverter here and set its public enum property.
}

Resources