WPF Combobox will not send Selection Back to View Model [duplicate] - c#

This question already has answers here:
DataGrid DataGridTemplateColumn ComboBox
(1 answer)
ComboBox SelectedItem property updating all other comboboxes in my Datagrid row
(1 answer)
Closed 2 years ago.
I have a WPF application that has a Combobox that is supposed to be bound to a boolean value. The UI should have a combobox with just two choices to choose from. Here is what I have so far. The XAML:
<DataGridTemplateColumn Header="Instance" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type dataModel:ParameterSetting}">
<Grid Visibility="{Binding IsPlaceholder, Converter={StaticResource BoolToVisInvert}}">
<ComboBox SelectedIndex="{Binding Instance, Mode=TwoWay, Converter={StaticResource ConvBoolToInstance}}">
<ComboBoxItem>Instance</ComboBoxItem>
<ComboBoxItem>Type</ComboBoxItem>
</ComboBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The referenced converter:
[ValueConversion(typeof(bool), typeof(int))]
public class ConvBoolToInstance : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) return 0;
if (!(value is bool e))
throw new ArgumentException(#"Value was not a boolean and could not be converted", nameof(value));
return e ? 0 : 1;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) return true;
if (value == null || !(value is int i))
throw new ArgumentException(#"Value was not an integer and could not be converted", nameof(value));
return i == 0;
}
}
And the property that it's trying to bind to:
/// <summary>
/// If the parameter should be added as instance
/// </summary>
public bool Instance
{
get => _instance;
set
{
if (_instance == value) return;
_instance = value;
OnPropertyChanged(nameof(Instance));
}
}
When I debug this I get the correct choices in the combobox. I can change the default of the field and the combobox displays the correct item, plus I can set a breakpoint on the getter for the property and it's hit so I know that is correctly bound and going to the property I want it to. However, when I change the combobox value it changes the UI but doesn't push back to the property value on the object. I have set break points in both the property setter and the converter and neither are hit. From what I can tell it's just not pushing it back which is what it should do with a two way binding...
What am I missing?
EDIT
The converter is thanks to this answer but I have also tried biding to it as a SelectedValue with boolean combobox items but nothing seems to make it bind back...
EDIT
I should also mention this is inside a DataGrid. Updated question to show more of the XAML

Ok finally figured this out and a bit of a face palm, but posting the answer to help anyone in my same position. The answer was to use UpdateSourceTrigger=PropertyChanged. I believe it defaults to LostFocus which is fine but in this case it was REALLY delayed. Like I selected something in the ComboBox, then clicked into another cell to edit it, then clickout out of THAT cell which apparently finally took focus because THEN it committed. Not sure why it would be so delayed like that but once I switched to PropertyChanged it worked like a charm.
Here is my final binding (after figuring this out I was also able to just use normal binding without the converter):
SelectedItem="{Binding Instance, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

Related

How to check or uncheck checkboxes inside of a List Box WPF

I have a combobox which data source comes from a table in my DataBase. So, each item in my combo is an Object from the table. This Object have an attribute which corresponds to a string full of "1"s or "0"s. On the other hand I have a list of checkboxes inside of a ListBox with this template:
<ListBox Height="150" MinHeight="100" HorizontalAlignment="Center" Name="lstEstudios" VerticalAlignment="Top" Width="200"
ItemsSource="{Binding}" SelectionMode="Multiple" Margin="0,20,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Name="chkEstudios" Width="Auto" Content="{Binding Path=Nom_estudio}"
Checked="chkEstudios_Checked" Unchecked="chkEstudios_Unchecked"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I don´t know if it's possible but, that I want to do is, for each "1" or "0" in the attribute set the checkbox checked or unchecked depending if there is a "1" check the checkbox or if is "0" uncheck the checkbox, and so on... with all the checkboxes in the ListBox, how to do that ?
I tried the same thing with my own sample having a CustomTask class.
<ListBox ItemsSource="{Binding CustomTasks}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding TaskStatus, Converter={x:Static testApp:StatusToBooleanConverter.Instance}}" Content="{Binding TaskStatus}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
where the TaskStatus is a boolean of two values, i.e Completed and Pending.
and here is the code for the converter
public class StatusToBooleanConverter : IValueConverter
{
public static StatusToBooleanConverter Instance = new StatusToBooleanConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Status)
{
switch ((Status)value)
{
case Status.Completed:
return true;
case Status.Pending:
return false;
}
return null;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Just try this out. Hope that helps.
This is a good place to use a converter. If you have a property with 1's and 0's in it and you want to translate those to true/false for the checked attribute then try making converters to do the work. The basic idea behind a converter is to take an input value (from a bound property) and, as the name implies, convert it to a different value. You can make them as simple or complicated as you would like, and turning "1" into true and "0" into false should be pretty quick.
You will bind the IsChecked attribute of the checkbox to your source of 1's and 0's and in the binding also use the converter.
If you haven't made a value converter before here is a nice tutorial on making one: http://wpftutorial.net/ValueConverters.html

WPF DataBinding does not update property (just only one time at startup)

I´m having some trouble binding some items attributes.
I have comboboxes and a buttons in a itemscontrol. The combobox is for searching Localities by name. Thats why when the combobox is created, the property IsEditable is true, in order to let the user enter a name, and then press left-control to search that string in the database via WCF.
Then, when the combobox ItemSource.Count is al least 1, I block the combobox by setting IsEditable = false (using the DataBinding of the button). Thats when the button have to change the visibility from hidden to visible, because pressing the button set IsEditable to true again, and ables the user to input a name to search.
To achieve this, I have binded the combobox IsEditable with the button Visibility attribute, and used the following converter, which works:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
public class VisibilityToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (Visibility)value == Visibility.Visible ? false : true;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
As I said, the left control button search the localities, for that I´m using the keydown event:
private void ComboBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.LeftCtrl)
{
ComboBox cbx = sender as ComboBox;
LocationServiceClient locationService = new LocationServiceClient();
if (cbx != null)
{
cbx.ItemsSource = locationService.SeachLocalities(new SearchLocalitiesRequest { Search = cbx.Text, MaxItems = 20 }).Localities;
cbx.DisplayMemberPath = "LocalityName";
localityCombobox = cbx;
cbx.IsDropDownOpen = true;
}
}
}
As the Items of the Combobox changed, wouldn´t that have to affect the binding of the Button visibility?
The binding uses this converter, which works too, but only executes once, when I run the app. Thats the problem I´m having, it just does not update the button visibility, and leaves it on Hidden:
public class ItemsSourceCountToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var b = (int)value > 0 ? Visibility.Visible : Visibility.Hidden;
return b;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
This the image of the control, it might help you to get what I say:
Just in case, this is the xaml i used:
<ComboBox Name ="cbxLocality" Width="200" DisplayMemberPath="{Binding LocalityName}" IsEditable="{Binding ElementName= btnRemoveLocality, Path=Visibility, Converter={StaticResource VisibilityToBooleanConverter}}" KeyDown="ComboBox_KeyDown">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding LocalityName}"/>
<TextBlock FontSize="10">
<Run Text="CP: "/>
<Run Text="{Binding ZipCode}"/>
<Run Text=" | "/>
<Run Text="{Binding Province.ProvinceName}"/>
</TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Name ="btnRemoveLocality" Content="x" Visibility="{Binding ElementName= cbxLocality, Path=Items.Count, Converter={StaticResource ItemsSourceCountToVisibilityConverter}}" Click="Button_Click_3"></Button>
Does it work to call .DataBind() on cbx when changing ItemSource?
Edit: I would bind the visibility for path Items.Count, instead of just Items, and make the converter handle the integer instead of the Item-list. Because the Count-property triggers the PropertyChanged-event, and the list itself will not if an element is added/removed.
Edit 2: Declare the ObservableCollection of you items as a public property outside the method itself, so it will have public scope. And set it as ItemsSource. Then you won't have to change the ItemSource-property, only the collection itself.

How to bind appbar's visibility by code?

How is it possible to set MultiApplicationBarBehavior.IsVisible binding in code?
The problem: if to bind it via xaml, it would be blinking even if binded value is false.
EDIT1: So, what i'm binding to?
<mainPivot:SplashScreenControl
Opacity="1"
Name="SplashScreen"
Visibility="{Binding Opacity, ElementName=SplashScreen, Converter={StaticResource DoubleToVisibilityConverter}}"
/>
<cimbalino:MultiApplicationBarBehavior
SelectedIndex="{Binding PivotIndex}"
IsVisible="{Binding Opacity, ElementName=SplashScreen, Converter={StaticResource DoubleToBooleanInversedConverter}}"
>
Splashscreen: Visibility is binded to Opacity, because Visible object with Opacity=0 is still handling input.
Appbar is just binded to Splashscreen's Opacity. No codebehind at all (just commented out everything). However, appbar is blinking during the page load. That's why i want to set it false by default and later to bind by code.
The only case, when appbar is not blinking is when it is binded to custom property, which is set to false during initialization
<cimbalino:MultiApplicationBarBehavior
SelectedIndex="{Binding PivotIndex}"
IsVisible="{Binding IsSplashScreenVisible, Converter={StaticResource BooleanInversedConverter}}"
>
Converter:
public class DoubleToBooleanInversedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return true;
return !((value as double?) > 0);
}
}
I don't think doing the binding in code will help, did you made sure that the value your are binding to is set to false when the contructor of the page is executed (and that the DataContext is set in the contructor)?
If you are binding to some object proeperty which is null in the contructor you could add FallbackValue=false on the binding.
If you don't find any other solution here is how to create the same binding in code:
Binding binding = new Binding("Opacity");
binding.ElementName = "SplashScreen";
binding.Converter = new DoubleToBooleanInversedConverter();
multiAppBarBehavior.SetBinding(MultiApplicationBarBehavior.IsVisibleProperty, binding);
(where multiAppBarBehavior will be the MultiApplicationBarBehavior control name)

Add a single row containing text taking up entire area of WPF Datagrid

I have a WPF datagrid and I would like to display a message saying "No Records Found" in the middle of the Datagrid.
One way I can see of doing this is to add a single datarow that spans all the columns and whos height is the height of the datagrid, then centre the text in the row.
The main thing Im struggling with is programatically adding the row to teh datagrid.
Is this possible or is there an easier way to do this?
Put the 'No Records Found' message in a TextBlock instead, and show/hide the TextBlock depending on whether the DataGrid has data.
<Grid>
<DataGrid x:Name="_dataGrid" />
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding ElementName=_dataGrid, Path=HasItems, Converter={StaticResource BooleanToInverseVisibilityConverter}, Mode=OneWay}">
No Records Found
</TextBlock>
</Grid>
EDIT: Source for BooleanToInverseVisibilityConverter (I have a bunch of handy converters like this always available to me in my WPF projects)
public class BooleanToInverseVisibilityConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return (bool) value ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return null;
}
}
You can add items to the datagrid programatically like this:
myDataGrid.Items.Add(new MyDataGridItem());
If you have set the ItemsSource of the datagrid to some collection or something, you need to modify that collection instead. You can do that like this:
myCollection.Add(new MyDataGridItem());
myDataGrid.ItemsSource = null;
myDataGrid.ItemsSource = myCollection;

GridView CheckBox binding

How to change the value of a text block inside a grid view in WPF based on the check box selection . Grid view in WPF is populated from a sql table which has ID and Value as columns.Value here is YES or NO.I am using linq to sql .
I have a check box associated to each ID in the grid view.when a user selects some rows ,i have to save the changes back to the database.
So based on the selection i have to change the value field in the row in this fashion:
If the text in the "Value" field of the grid view is "YES" then i have to change it to "NO"
If the text in the "Value" field of the grid view is "NO" then i have to change it to "YES"
I am able to populate data into the gridview ,but i am not sure whether my questions in the above scenario will fit in WPF and c#.Need some guidance.
The best way to do this is to bind both the Text block and the checkbox to the same backend field in the data model and then to use code converters.
Here is a simple example.
Say you have the following simple view model with one bool property:
class SimpleViewModel: INotifyPropertyChanged
{
private bool _checked;
// The property to bind to
public bool Checked
{
get { return _checked; }
set { _checked = value; OnPropertyChanged("Checked"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Here is also a simple page with a text block and a text field that both bind to the same backend field.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:SimpleViewModel x:Key="simpleViewModel" />
<local:BoolToStringConverter x:Key="boolToStringConverter" />
</Window.Resources>
<Grid DataContext="{StaticResource simpleViewModel}">
<StackPanel>
<TextBlock Text="{Binding Checked, Converter={StaticResource boolToStringConverter}}" />
<CheckBox Content="something" IsChecked="{Binding Checked}" />
</StackPanel>
</Grid>
</Window>
Now notice that the text block binding statement contains a converter statement.
Text="{Binding Checked, Converter={StaticResource boolToStringConverter}}"
The converter here is very simple. It checks the value if it's true and returns Yes, otherwise returns NO.
public class BoolToStringConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) return null;
if ((bool)value == true)
return "YES";
else
return "NO";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// this scenario is not needed since the text block is read only
throw new NotImplementedException();
}
}
You need to do events. Click on the control and click the lightning bold and do it in the code behind in c#. The keyword is events. OnChanged, Onclicked, onrowchange, etc. is inside that properties box for that control and you change the value in the code.
Use two way binding to transfer changes from UI into database. Bind the checkboxes column to the Value field from SQL table. You will need a convertor for binding to transform from Yes/No into bool.
http://msdn.microsoft.com/en-us/magazine/cc163299.aspx#S3
http://msdn.microsoft.com/en-us/library/ms752347.aspx#data_conversion

Categories