DataBinding Collection and change element of collection - c#

I have a colection and a ListView to which I have binded a collection of objects:
<ListView ItemsSource="{Binding Levels}"... />
Here is a Levels collection:
private ObservableCollection<Level> _levels;
public ObservableCollection<Level> Levels
{
get { return _levels; }
set { SetProperty(ref _levels, value); }
}
And here is a Level class:
public class Level : BindableBase
{
private double _value;
public double Value
{
get { return _value; }
set { SetProperty(ref _value, value); }
}
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set { SetProperty(ref _isChecked, value); }
}
public SolidColorBrush ForegroundColor
{
get { return IsChecked ? new SolidColorBrush(Colors.Yellow) : new SolidColorBrush(Colors.BlueViolet); }
}
}
If I add element in the collection, the new element will be displayed in the ListView, but if I change the existing element of collection I can not see any change:
private void LvLevels_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (LvLevels.SelectedItem != null)
{
selectedLevel = (Level)LvLevels.SelectedItem;
foreach (var l in viewModel.Levels)
{
if (l.Value == selectedLevel.Value)
l.IsChecked = true; // it doesn't work
else
l.IsChecked = false;
}
// it works
// viewModel.Levels.Add(new Level { Value = 10, IsChecked = true});
}
Why and how can I fix it ?
Update
Here is my ItemTemplate:
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Source="../Assets/icons/зоны.png"
Margin="10 0 0 0"/>
<TextBlock x:Name="tblock" Text="{Binding Value}" Grid.Column="1" FontSize="30"
Foreground="{Binding ForegroundColor}" />
<!-- Style="{StaticResource ZoneButtonText}" -->
</Grid>
</DataTemplate>
</ListView.ItemTemplate>

If you expect the ForegroundColor to change (that's a guess), you need to make sure the WPF components know it has changed. You need to send a change notification for ForegroundColor when IsChecked changes, too.
Oh, and don't compare doubles using ==. Doubles are floating point variables.

Related

Prevent Listbox selection

I have a multi selection listbox where only certain combinations of items are permitted in selection.
How can I deny certain selection?
The listbox is bound to view model. I thought I could do it in property setter but is doesn't work.
XAML
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:ChangePropertyAction TargetObject="{Binding Mode=OneWay}" PropertyName="SelectedItems" Value="{Binding Path=SelectedItems, ElementName=TranslatorsListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In View Model
public System.Collections.IList SelectedItems
{
get
{
return SelectedModels;
}
set
{
//what here?
}
}
Edit:
I should have made clear, that I'm using multi selection. That's why I'm using interaction triggers and not just binding to SelectedItem. The linked answer is for single selection.
I've found that the easiest way was to not use SelectionMode nor SelectedItem, but instead utilize the Tapped event. From this you can get the "selected item".
I then set a bound property in the ViewModel that is used by the View to change the background of the selected item in the ListView if its one that I want to select. To do this effectively your ListView needs to be tied to an ObservableCollection of ViewModels (or a class that implements INotifyPropertyChanged).
Here's some rough code to illustrate what I mean:
In Xaml View code
<ListView
ItemsSource ="{Binding Values}"
ItemTapped="OnItemTapped"
HasUnevenRows="True"
RefreshAllowed="True"
SelectionMode="None"
>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid RowSpacing="0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*"/>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30*"/>
</Grid.RowDefinitions>
<BoxView Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" BackgroundColor="{StaticResource Blue}" IsVisible="{Binding IsSelected}" />
<Label Grid.Column="0" Grid.Row="0" HorizontalTextAlignment="Center" VerticalOptions="Center" Text="{Binding Destination}"/>
<Label Grid.Column="1" Grid.Row="0" HorizontalTextAlignment="Start" VerticalOptions="Center" Text="{Binding Value}"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In code behind:
private void OnItemTapped(object sender, ItemTappedEventArgs e)
{
var param = (ValueViewModel)e.Item;
if (((SourceViewModel)BindingContext).SelectValueCommand.CanExecute(param))
{
((SourceViewModel)BindingContext).SelectValueCommand.Execute(param);
}
}
Then these classes used - the first is the BindingContext for the View
public class SourceViewModel:INotifyPropertyChanged
{
public SourceViewModel (List<ValueViewModel> list)
{
SelectValueCommand = new Command<ValueViewModel>((value) => ExecuteSelectValueCommand(value), (value)=>!IsBusy);
}
private ObservableCollection<ValueViewModel> _values = new ObservableCollection<ValueViewModel>();
private bool _isSelected;
private sring _description;
public ICommand SelectValueCommand;
public bool IsBusy{get;set;}
public ObservableCollection<ValueViewModel> Values
{
get=>_values;
set
{
_values = value;
OnPropertyChanged(nameof(Values));
}
}
public void ExecuteSelectValueCommand(ValueViewModel value)
{
if(IsBusy) return;
item.IsSelected = CanValueBeSelected(value);
}
private bool CanValueBeSelected(ValueViewModel value)
{
var result = false;
//Some logic to determine if it can be selected
return result;
}
#region INotifyPropertyChanged implementation
// ...
#endregion
}
public class ValueViewModel:INotifyPropertyChanged
{
public ValueViewModel (string description, int value)
{
Description = description;
Value = value;
}
private int _value;
private bool _isSelected;
private sring _description;
public int Value
{
get=>_value;
set
{
_value=value;
OnPropertyChanged(nameof(Value));
}
}
public bool IsSelected
{
get=> _isSelected;
set
{
_isSelected= value;
OnPropertyChanged(nameof(IsSelected));
}
}
public string Description
{
get=> _description;
set
{
_description= value;
OnPropertyChanged(nameof(Description));
}
}
#region INotifyPropertyChanged implementation
// ...
#endregion
}

WPF button content is empty when shown

I am new to C#/WPF. There is a view with one button defined, when the view is initialized, buttons will display a set of reason codes got from DataContext (viewmodel), once any button is clicked, the code on it will be saved and passed forward for next processing.
Q: The text on buttons are totally empty, but the clicked code can be captured, so where the problem is about binding? Thanks.
XAML:
<Button x:Name="btnReason" Command="{Binding DataContext.SelectCommand, RelativeSource={RelativeSource AncestorType=v:View, Mode=FindAncestor}}" CommandParameter="{Binding}" Width="190" Height="190" >
<Border Background="Transparent">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock x:Name="Reason" Grid.Row="0" Text="{Binding ?????}" TextWrapping="Wrap" />
</Grid>
</Border>
</Button>
The code on C#:
public class ReasonsViewModel : ViewModel
{
private IEnumerable<string> m_Names;
public IEnumerable<string> Names
{
get { return m_Names; }
set
{
if (m_Names != value)
{
m_Names = value;
OnPropertyChanged(() => Names);
}
}
}
private string m_SelectedName;
public string SelectedName
{
get { return m_SelectedName; }
set
{
if (m_SelectedName != value)
{
m_SelectedName = value;
OnPropertyChanged(() => SelectedName);
}
}
}
public DelegateCommand SelectCommand { get; private set; }
public ReasonsViewModel()
{
SelectCommand = new DelegateCommand(p => SelectCommandExecute(p));
}
private bool m_Processing;
private void SelectCommandExecute(object item)
{
if (m_Processing) return;
try
{
m_Processing = true;
var name = item as string;
if (name == null) return;
SelectedName = name;
}
finally
{
m_Processing = false;
}
}
}
If I understood your question correctly than your property text in your TextBlock should be bound to SelectedName.
The problem is that your CommandParameter is bound to DataContext. That's what an empty {Binding} statement bounds to. This means your command handler always returns after the null check.
I also suggest that you change your Names proeprty from IEnumerable<string> to ObservableCollection<string>.
ObservableCollection raises events on any additions or removalof items inside and WPF components can bind to these events.

c# uwp textblock text property not updating UI during runtime

I'm selecting an item from a combobox to filter a listview of items. The items contain values, and are displayed in a View depending on the filter selection.
<ComboBox Name="YearComboBox" ItemsSource="{x:Bind StudentEnrollment.Years, Mode=OneWay}" SelectedValue="{x:Bind StudentEnrollment.SelectedYear, Mode=TwoWay}”/>
<ListView ItemsSource="{x:Bind StudentEnrollment.FilteredStudentEnrollments, Mode=OneWay}" SelectedIndex="{x:Bind StudentEnrollment.SelectedIndex, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModels:StudentViewModel" >
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind Score01, Mode=OneWay}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class StudentEnrollmentViewModel : NotificationBase
{
StudentEnrollment StudentEnrollment;
public StudentEnrollmentViewModel()
{
}
private ObservableCollection<StudentEnrollmentViewModel> _StudentEnrollments;
public ObservableCollection<StudentEnrollmentViewModel> StudentEnrollments
{
get { return _StudentEnrollments; }
set
{
SetProperty(ref _StudentEnrollments, value);
}
}
private ObservableCollection<StudentEnrollmentViewModel> _FilteredStudentEnrollments;
public ObservableCollection<StudentEnrollmentViewModel> FilteredStudentEnrollments
{
get { return _FilteredStudentEnrollments; }
set
{
SetProperty(ref _FilteredStudentEnrollments, value);
}
}
private string _selectedYear;
public string SelectedYear
{
get
{
return _selectedYear;
}
set { SetProperty(ref _selectedYear, value);
{ RaisePropertyChanged(SelectedYear); }
RefreshFilteredStudentEnrollmentData(); }
}
private double _Score01;
public double Score01
{
get
{
_Score01 = FilteredStudentEnrollments.Where(y => y.Year == SelectedYear).Select(s => s.Score01).Sum();
return _Score01;
}
}
private void RefreshFilteredStudentEnrollmentData()
{
var se = from seobjs in StudentEnrollments
where seobjs.Year == SelectedYear
select seobjs;
if (FilteredStudentEnrollments.Count == se.Count()) || FilteredStudentEnrollments == null
return;
FilteredStudentEnrollments = new ObservableCollection<StudentEnrollmentViewModel>(se);
}
As expected I can sum the filtered values, and display the total in a TextBlock text property per listview column when the page loads.
<TextBlock Text="{x:Bind StudentEnrollment.Score01, Mode=OneWay}"/>
The issue's the Textblock UI does not update/display the changing property value in the viewmodel via a combobox selection. I sure I'm missing something, or have some logic backwards, hoping some fresh eyes can help.
I am not sure which class is which, because there seems to be some mix up between StudentEnrollmentViewModel and StudentViewModel, but I think I see the reason for the problem.
The Score01 property is a get-only property (actually you can get rid of the _Score01 field and just return the value). Its value is dependent on the FilteredStudentEnrollments and SelectedYear properties. But the UI does not know that so you have to notify it when these properties change that it should bind a new value of Score01 as well:
private ObservableCollection<StudentEnrollmentViewModel> _FilteredStudentEnrollments;
public ObservableCollection<StudentEnrollmentViewModel> FilteredStudentEnrollments
{
get { return _FilteredStudentEnrollments; }
set
{
SetProperty(ref _FilteredStudentEnrollments, value);
RaisePropertyChanged("Score01");
}
}
private string _selectedYear;
public string SelectedYear
{
get
{
return _selectedYear;
}
set
{
SetProperty(ref _selectedYear, value);
RaisePropertyChanged("Score01");
RefreshFilteredStudentEnrollmentData();
}
}
public double Score01
{
get
{
return FilteredStudentEnrollments.
Where(y => y.Year == SelectedYear).Select(s => s.Score01).Sum();
}
}
Notice I have changed the RaisePropertyChanged call to pass in "Score01", as the method expects the name of the property, whereas you were previously passing the value of the SelectedYear property, which meant the PropertyChanged event executed for something like 2018 instead of the property itself. Also note that SetProperty method internally calls RaisePropertyChanged for SelectedYear property as well.

Databound list box not updating to correct values from observable collection WPF

I am new to WPF so could be something very simple I've missed.
I have a list box which is holding databound Class properties from a static observableCollection<myClass>. The collection is being updated several times a second from a network stream source and from what I can tell from debugging, the collection is being updated properly. The declaration is as follows: static ObservableCollection<PumpItem> pumpCollection = new ObservableCollection<PumpItem>(); where PumpItem is the name of my class.
That is not to say that the listbox isn't displaying anything however, it is updating to display any new values added to the collection but these only ever reflect the properties of the first moment they enter the collection.
The values of the listbox are bound as such:
<ListBox x:Name="pumpListBox" ItemsSource="{Binding PumpCollection}" Grid.IsSharedSizeScope="True" Margin="0,0,153,0">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="ID" />
<ColumnDefinition SharedSizeGroup="State" />
<ColumnDefinition SharedSizeGroup="Selection" />
<ColumnDefinition SharedSizeGroup="Fuel Pumped" />
<ColumnDefinition SharedSizeGroup="Cost" />
</Grid.ColumnDefinitions>
<TextBlock Margin="2" Text="{Binding PumpID}" Grid.Column="0"/>
<TextBlock Margin="2" Text="{Binding State}" Grid.Column="1"/>
<TextBlock Margin="2" Text="{Binding Selection}" Grid.Column="2"/>
<TextBlock Margin="2" Text="{Binding FuelPumped}" Grid.Column="3"/>
<TextBlock Margin="2" Text="{Binding FuelCost}" Grid.Column="4"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I have this declaration in the code behind in order to set resources to the collection:
public static ObservableCollection<PumpItem> PumpCollection
{
get { return pumpCollection; }
}
In my `MainWindow() constructor I've set:
this.DataContext = this;
before InitialiseComponent(); and my background worker thread to receive network inputs to update the list:worker.RunWorkerAsync();`
This background thread then loops continuously updates the collection from the stream and invokes a resource update:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
//background tasks
Thread.Sleep(500); //allows the ui time to update and initialise
string s_decryptedMessage = string.Empty;
App.Current.Dispatcher.Invoke((Action)(() =>
{
Resources["PumpCollection"] = pumpCollection;
}));
while (true)
{
byteMessage = Recieve(stream);//get the number of pumps about to be recieved
Interact(byteMessage); //signal server to update pumplist
}
}
If it helps, my class code is thus:
namespace PoSClientWPF
{
public enum pumpState
{
Available,
Waiting,
Pumping,
Paying
};
public enum fuelSelection
{
Petrol,
Diesel,
LPG,
Hydrogen,
None
};
public class PumpItem
{
private string pumpID;
public string PumpID
{
get
{
return pumpID;
}
set
{
pumpID = value;
}
}
private double fuelPumped;
public double FuelPumped
{
get
{
return fuelPumped;
}
set
{
fuelPumped = value;
}
}
private double fuelCost;
public double FuelCost
{
get
{
return fuelCost;
}
set
{
fuelCost = Math.Round(value, 2); //cost to two DP
}
}
private fuelSelection selection;
public fuelSelection Selection
{
get
{
return selection;
}
set
{
selection = value;
}
}
private pumpState state;
public pumpState State
{
get
{
return state;
}
set
{
state = value;
}
}
public PumpItem(string _ID, pumpState _state, fuelSelection _selection)
{
this.pumpID = _ID;
this.FuelPumped = 0;
this.FuelCost = 0;
this.Selection = _selection;
this.State = _state;
}
}
}
As I said, I'm very new to WPF so would greatly appreciate any guidance or solutions. Thanks.
Ash was completely correct but here is a quick example for you to skim. This is and example of aViewModelBase which typically all ViewModels inherit from. From one of my repos. This how you would call your OnChangedEvent.
private sample _Item;
public sample Item
{
get
{
return _Item;
}
set
{
if (_Item != value)
{
_Item = value;
OnPropertyChanged("Item");
}
}
}
This will get it where you update everything when properties change.

Displaying properties of a class in a listbox from a databound Observable collection WPF

I am currently attempting to display items from an ObservableCollection(myClass). The class itself just has some public string properties. I know that the collection is being updated from a stream source correctly but for some reason it's not updating the list box with the properties I want it to. It's very likely that my XAML has some error in it:
<Window x:Class="PoSClientWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid Margin="10">
<ListBox x:Name="pumpListBox" ItemsSource="{Binding PumpCollection}" Grid.IsSharedSizeScope="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="ID" />
<ColumnDefinition SharedSizeGroup="State" />
</Grid.ColumnDefinitions>
<TextBlock Margin="2" Text="{Binding pumpID}" Grid.Column="0"/>
<TextBlock Margin="2" Text="{Binding state}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
From researching other posts about this very error. I've included adding this.DataContext = this; to my MainWindow as well as having:
public ObservableCollection<PumpItem> PumpCollection
{
get { return pumpCollection; }
}
In order to bind the ItemsSource to it. I think there is an error in how I'm declaring the bindings in XAML but I'm not sure. I'm trying to add the properties pumpID and state to the listbox from the class instance.
The class pumpItem is shown below:
public enum pumpState
{
Available,
customerWaiting,
Pumping,
customerPaying
};
public enum fuelSelection
{
Petrol,
Diesel,
LPG,
Hydrogen,
None
};
public class PumpItem
{
public string pumpID;
public double fuelPumped;
public double fuelCost;
public fuelSelection selection;
public pumpState state;
public PumpItem(string _ID)
{
this.pumpID = _ID;
this.fuelPumped = 0;
this.fuelCost = 0;
this.selection = fuelSelection.None;
this.state = pumpState.Available;
}
}
Any pointers or help much appreciated.
You can't bind to fields. Change these to public properties
public class PumpItem
{
private string pumpID;
public string PumpId
{
get
{
return pumpId;
}
set
{
pumpId = value;
}
}
private double fuelPumped;
public double FuelPumped
{
get
{
return fuelPumped;
}
set
{
fuelPumped = value;
}
}
private double fuelCost;
public double FuelCost
{
get
{
return fuelCost;
}
set
{
fuelCost= value;
}
}
public fuelSelection selection;
public fuelSelection Selection
{
get
{
return selection;
}
set
{
selection = value;
}
}
public pumpState state;
public pumpState State
{
get
{
return state;
}
set
{
state = value;
}
}
public PumpItem(string _ID)
{
this.PumpID = _ID;
this.FuelPumped = 0;
this.FuelCost = 0;
this.Selection = fuelSelection.None;
this.State = pumpState.Available;
}
}
XAML
<TextBlock Margin="2" Text="{Binding PumpID}" Grid.Column="0"/>
<TextBlock Margin="2" Text="{Binding State}" Grid.Column="1"/>
Check the Output console for binding errors

Categories