wpf UI not updating? - c#

Going through:
WPF binding not updating the view
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netcore-3.1
and
WPF DataContext updated but UI not updated
I still can't see why the UI is not updating in the following case (my best guess is that the DataContext of the Grid to be updated is not updated) and am loosing my mind:
AppliedJobsModel.cs (has IPropertyChange implemented as some of the answers suggest):
public class AppliedJobsModel { }
public class AppliedJob : INotifyPropertyChanged
{
private string appliedDate;
private string url;
private string company;
private string description;
private string contact;
private string stack;
private string response;
private string interviewDate;
public AppliedJob(string[] entries)
{
appliedDate = entries[Consts.APPLIED_DATE_INDEX];
url = entries[Consts.URL_INDEX];
company = entries[Consts.COMPANY_INDEX];
description = entries[Consts.DESCRIPTION_INDEX];
contact = entries[Consts.CONTACT_INDEX];
stack = entries[Consts.STACK_INDEX];
response = entries[Consts.RESPONSE_INDEX];
interviewDate = entries[Consts.INTERVIEWDATE_INDEX];
}
public string AppliedDate
{
get {
return appliedDate;
}
set {
if (appliedDate != value)
{
appliedDate = value;
RaisePropertyChanged("AppliedDate");
}
}
}
public string Url
{
get
{
return url;
}
set
{
if (url != value)
{
url = value;
RaisePropertyChanged("Url");
}
}
}
public string Company
{
get
{
return company;
}
set
{
if (company != value)
{
company = value;
RaisePropertyChanged("Company");
}
}
}
public string Description
{
get
{
return description;
}
set
{
if (description != value)
{
description = value;
RaisePropertyChanged("Description");
}
}
}
public string Contact
{
get
{
return contact;
}
set
{
if (contact != value)
{
contact = value;
RaisePropertyChanged("Contact");
}
}
}
public string Stack
{
get
{
return stack;
}
set
{
if (stack != value)
{
stack = value;
RaisePropertyChanged("Stack");
}
}
}
public string Response
{
get
{
return response;
}
set
{
if (response != value)
{
response = value;
RaisePropertyChanged("Response");
}
}
}
public string InterviewDate
{
get
{
return interviewDate;
}
set
{
if (interviewDate != value)
{
interviewDate = value;
RaisePropertyChanged("InterviewDate");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
AppliedJobsViewModel.cs (has an observable collection that gets correctly updated when a button is clicked (in dbg)):
class AppliedJobsViewModel
{
private TexParser texParser;
public AppliedJobsViewModel() {
// TODO:
// -- do nothing here
}
public ObservableCollection<AppliedJob> AppliedJobsCollection
{
get;
set;
}
private ICommand _openTexClick;
public ICommand OpenTexClick
{
get
{
return _openTexClick ?? (_openTexClick = new CommandHandler(() => ReadAndParseTexFile(), () => CanExecute));
}
}
public bool CanExecute
{
get
{
// check if executing is allowed, i.e., validate, check if a process is running, etc.
return true;
}
}
public async Task ReadAndParseTexFile()
{
if (texParser == null)
{
texParser = new TexParser();
}
// Read file asynchronously here
await Task.Run(() => ReadFileAndUpdateUI());
}
private void ReadFileAndUpdateUI()
{
texParser.ReadTexFile();
string[][] appliedJobsArray = texParser.getCleanTable();
// Use this:
// https://rachel53461.wordpress.com/2011/09/17/wpf-grids-rowcolumn-count-properties/
// Update collection here
List<AppliedJob> appliedJobsList = createAppliedJobsListFromTable(appliedJobsArray);
AppliedJobsCollection = new ObservableCollection<AppliedJob>(appliedJobsList);
}
private List<AppliedJob> createAppliedJobsListFromTable(string[][] table)
{
List<AppliedJob> jobsList = new List<AppliedJob>();
for (int i = 0; i < table.Length; i++)
{
jobsList.Add(new AppliedJob(table[i]));
}
return jobsList;
}
}
AppliedJobsView.xaml:
<UserControl x:Class="JobTracker.Views.AppliedJobsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:JobTracker.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Name="appliedJobsGrid" Grid.Row="1" Grid.Column="1" Background="#50000000" Margin="10,10,10,10">
<ItemsControl ItemsSource = "{Binding Path = AppliedJobsCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = AppliedDate, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = Url, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = Company, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = Description, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = Contact, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = Stack, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = Response, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = InterviewDate, Mode = TwoWay}" Width = "100" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
TrackerHome.xaml (main page/uses the user control):
<Grid Grid.Row="1" Grid.Column="1">
<views:AppliedJobsView x:Name = "AppliedJobsControl" Loaded = "AppliedJobsViewControl_Loaded" />
</Grid>
TrackerHome.cs:
public TrackerHome()
{
InitializeComponent();
// Set data context here (https://stackoverflow.com/questions/12422945/how-to-bind-wpf-button-to-a-command-in-viewmodelbase)
// https://stackoverflow.com/questions/33929513/populate-a-datagrid-using-viewmodel-via-a-database
if (appliedJobsViewModel == null)
{
appliedJobsViewModel = new AppliedJobsViewModel();
}
this.DataContext = appliedJobsViewModel;
//AppliedJobControl.DataContext = appliedJobsViewModel;
}
private void AppliedJobsViewControl_Loaded(object sender, RoutedEventArgs e)
{
if (appliedJobsViewModel == null)
{
appliedJobsViewModel = new AppliedJobsViewModel();
}
AppliedJobsControl.DataContext = appliedJobsViewModel;
}

You are setting a new value of property here:
AppliedJobsCollection = new ObservableCollection<AppliedJob>(appliedJobsList);
but it's a simple auto-property without notification.
Make it full property (view model needs to implement INotifyPropertyChange):
ObservableCollection<AppliedJob> _appliedJobsCollection =
new ObservableCollection<AppliedJob>(); // empty initially
public ObservableCollection<AppliedJob> AppliedJobsCollection
{
get => _appliedJobsCollection;
set
{
_appliedJobsCollection = value;
RaisePropertyChanged(nameof(AppliedJobsCollection));
}
}
How does the full property behave? Is it as if all entries in each item in the collection have been changed (and thus have their properties changed)?
See this pseudo-code.
// given that AppliedJobsCollection is already initialized
// modify existing collection -> works
// bindings was subscribed to CollectionChanged event and will update
AppliedJobsCollection.Add(new AppliedJob(...));
// change item property -> works
// you implement INotifyPropertyChanged for items
// bindings was subscribed to that and will update
AppliedJobsCollection[0].Company = "bla";
// new instance of collection -> ... doesn't works
// how bindings can update?
AppliedJobsCollection = new ObservableCollection<AppliedJob>(...);
For last scenario to work you need to implement INotifyPropertyChanged for a class containing AppliedJobsCollection property and rise notification.

Related

WinUI x:Bind to Model property not updating UI

This is the first time I have used WinUI. I have all of the information I need pulling in, but the binding back to the UI to display the data after async functions isn't updating the UI controls.
I have a view model that is handling the streams to load in the string from the user's selected file, and storing that List<string> in the model object. The model object implements INotifyPropertyChanged. I must not understanding completely the relationship between x:bind and INotifyPropertyChanged because nothing is set from the view to the PropertyChanged event.
public class PCCBill : INotifyPropertyChanged
{
private string importFileName;
private List<string> lines;
private string originalFileText;
//private List<string> formattedLines;
public PCCBill(string displayFileName)
{
this.ImportFileName = displayFileName;
}
public string ImportFileName
{
get { return importFileName; }
set
{
this.importFileName = value;
RaisePropertyChanged(nameof(ImportFileName));
}
}
public List<string> Lines
{
get { return lines; }
set
{
this.lines = value;
string txt = "";
foreach (string line in this.lines)
{
txt += line + Environment.NewLine;
}
this.OriginalFileText = txt;
}
}
public string OriginalFileText
{
get { return originalFileText; }
set
{
this.originalFileText = value;
RaisePropertyChanged(nameof(OriginalFileText));
}
}
/*public string FormattedFileText
{
get
{
string txt = "";
foreach(string line in this.formattedLines)
{
txt += line + Environment.NewLine;
}
return txt;
}
}*/
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
I have the x:Bind set in the View with OneWay Mode:
<TextBlock Grid.Row="1" x:Name="FileNameTxtBlock" VerticalAlignment="Center"
Margin="15,0" Text="{x:Bind ViewModel.Bill.ImportFileName, Mode=OneWay}"/>
<TextBlock x:Name="OrgPreviewTxtBlock" Text="{x:Bind ViewModel.Bill.OriginalFileText, Mode=OneWay}"/>

custom search for combobox

I am creating a WPF app containing a ComboBox which shows some data. I want to use the combobox-integrated text seach. But the problem is, if the user searchs for "llo", the list should show all items, containing this text snippet, like "Hallo", "Hello", "Rollo" ... But the search returns no result because the property name of no item starts with "llo". Has somebody an idea how to achieve this?
I am using the MVVM-pattern. The view is binded to a collection of DTOs (property of the viewmodel), in the DTO there are two properties which are relevant for the search.
<ComboBox
ItemsSource="{Binding Path=Agencies}"
SelectedItem="{Binding Path=SelectedAgency}"
IsTextSearchEnabled="True"
DisplayMemberPath="ComboText"
IsEnabled="{Binding IsReady}"
IsEditable="True"
Grid.Column="0"
Grid.Row="0"
IsTextSearchCaseSensitive="False"
HorizontalAlignment="Stretch">
</ComboBox>
public class Agency
{
public int AgencyNumber { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string ContactPerson { get; set; }
public string ComboText => $"{this.AgencyNumber}\t{this.Name}";
}
Ginger Ninja | Kelly | Diederik Krols definitely provide a nice all in one solution, but it may be a tad on the heavy side for simple use cases. For example, the derived ComboBox gets a reference to the internal editable textbox. As Diederik points out "We need this to get access to the Selection.". Which may not be a requirement at all. Instead we could simply bind to the Text property.
<ComboBox
ItemsSource="{Binding Agencies}"
SelectedItem="{Binding SelectedAgency}"
Text="{Binding SearchText}"
IsTextSearchEnabled="False"
DisplayMemberPath="ComboText"
IsEditable="True"
StaysOpenOnEdit="True"
MinWidth="200" />
Another possible improvement is to expose the filter, so devs could easily change it. Turns out this can all be accomplished from the viewmodel. To keep things interesting I chose to use the Agency's ComboText property for DisplayMemberPath, but its Name property for the custom filter. You could, of course, tweak this however you like.
public class MainViewModel : ViewModelBase
{
private readonly ObservableCollection<Agency> _agencies;
public MainViewModel()
{
_agencies = GetAgencies();
Agencies = (CollectionView)new CollectionViewSource { Source = _agencies }.View;
Agencies.Filter = DropDownFilter;
}
#region ComboBox
public CollectionView Agencies { get; }
private Agency selectedAgency;
public Agency SelectedAgency
{
get { return selectedAgency; }
set
{
if (value != null)
{
selectedAgency = value;
OnPropertyChanged();
SearchText = selectedAgency.ComboText;
}
}
}
private string searchText;
public string SearchText
{
get { return searchText; }
set
{
if (value != null)
{
searchText = value;
OnPropertyChanged();
if(searchText != SelectedAgency.ComboText) Agencies.Refresh();
}
}
}
private bool DropDownFilter(object item)
{
var agency = item as Agency;
if (agency == null) return false;
// No filter
if (string.IsNullOrEmpty(SearchText)) return true;
// Filtered prop here is Name != DisplayMemberPath ComboText
return agency.Name.ToLower().Contains(SearchText.ToLower());
}
#endregion ComboBox
private static ObservableCollection<Agency> GetAgencies()
{
var agencies = new ObservableCollection<Agency>
{
new Agency { AgencyNumber = 1, Name = "Foo", Title = "A" },
new Agency { AgencyNumber = 2, Name = "Bar", Title = "C" },
new Agency { AgencyNumber = 3, Name = "Elo", Title = "B" },
new Agency { AgencyNumber = 4, Name = "Baz", Title = "D" },
new Agency { AgencyNumber = 5, Name = "Hello", Title = "E" },
};
return agencies;
}
}
The main gotchas:
When the user enters a search and then selects an item from the filtered list, we want SearchText to be updated accordingly.
When this happens, we don't want to refresh the filter. For this demo, we're using a different property for DisplayMemberPath and our custom filter. So if we would let the filter refresh, the filtered list would be empty (no matches are found) and the selected item would be cleared as well.
On a final note, if you specify the ComboBox's ItemTemplate, you'll want to set TextSearch.TextPath instead of DisplayMemberPath.
If you refer to this answer
This should put you in the correct direction. It operated in the manner i believe you need when i tested it. For completeness ill add the code:
public class FilteredComboBox : ComboBox
{
private string oldFilter = string.Empty;
private string currentFilter = string.Empty;
protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox;
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
var view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += FilterItem;
}
if (oldValue != null)
{
var view = CollectionViewSource.GetDefaultView(oldValue);
if (view != null) view.Filter -= FilterItem;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Tab:
case Key.Enter:
IsDropDownOpen = false;
break;
case Key.Escape:
IsDropDownOpen = false;
SelectedIndex = -1;
Text = currentFilter;
break;
default:
if (e.Key == Key.Down) IsDropDownOpen = true;
base.OnPreviewKeyDown(e);
break;
}
// Cache text
oldFilter = Text;
}
protected override void OnKeyUp(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Up:
case Key.Down:
break;
case Key.Tab:
case Key.Enter:
ClearFilter();
break;
default:
if (Text != oldFilter)
{
RefreshFilter();
IsDropDownOpen = true;
}
base.OnKeyUp(e);
currentFilter = Text;
break;
}
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
ClearFilter();
var temp = SelectedIndex;
SelectedIndex = -1;
Text = string.Empty;
SelectedIndex = temp;
base.OnPreviewLostKeyboardFocus(e);
}
private void RefreshFilter()
{
if (ItemsSource == null) return;
var view = CollectionViewSource.GetDefaultView(ItemsSource);
view.Refresh();
}
private void ClearFilter()
{
currentFilter = string.Empty;
RefreshFilter();
}
private bool FilterItem(object value)
{
if (value == null) return false;
if (Text.Length == 0) return true;
return value.ToString().ToLower().Contains(Text.ToLower());
}
}
The XAML I used to test:
<Window x:Class="CustomComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CustomComboBox"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowVM/>
</Window.DataContext>
<Grid>
<local:FilteredComboBox IsEditable="True" x:Name="MyThing" HorizontalAlignment="Center" VerticalAlignment="Center"
Height="25" Width="200"
ItemsSource="{Binding MyThings}"
IsTextSearchEnabled="True"
IsEnabled="True"
StaysOpenOnEdit="True">
</local:FilteredComboBox>
</Grid>
My ViewModel:
public class MainWindowVM : INotifyPropertyChanged
{
private ObservableCollection<string> _myThings;
public ObservableCollection<string> MyThings { get { return _myThings;} set { _myThings = value; RaisePropertyChanged(); } }
public MainWindowVM()
{
MyThings = new ObservableCollection<string>();
MyThings.Add("Hallo");
MyThings.Add("Jello");
MyThings.Add("Rollo");
MyThings.Add("Hella");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
If it doesnt meet your exact needs im sure you can edit it. Hope this helps.
Use the .Contains method.
This method will return true if the string contains the string you pass as a parameter.
Else it will return false.
if(agency.Title.Contains(combobox.Text))
{
//add this object to the List/Array that contains the object which will be shown in the combobox
}

How do I bind an Observable Collection of dynamic data to an array of UserControls

I've been teaching myself C# and WPF for a few months and I've been stuck on this latest problem for a few days. I receive a stream of data (bytes of numbers and flags) about numerous units every second. I want to display this data using a UserControl and update it with data bindings. Since the project will require 42 such units displayed in a window, I have placed the UserControls in an array. But, I can't get the data binding to work. I've stripped out as much code as I could but I know this is still a lot remaining. The problem is with the bindings. I just can't figure out the correct syntax.
Here is My UserControl
<UserControl x:Name="TestUserControl"
x:Class="Test.UserControl1">
<Label x:Name="userControlNameLabel" Content="UNIT x" />
<Label x:Name="powerLabel" Content=" Powered"/>
<Label Content="V in"/>
<Label x:Name="vinData" Content="{Binding Path=VinDisplay, ElementName=UserControl1}" >
</Grid>
</UserControl>
Here is my UserControl code behind:
namespace Test
{
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public UserControl1()
{
InitializeComponent();
}
private byte vin;
public byte Vin
{
get { return (byte)GetValue(VinProperty); }
set
{ SetValue(VinProperty, (byte)value);
NotifyPropertyChanged("Vin");
}
}
private int power;
public int Power
{
get { return power; }
set
{
power = value;
if (power == 1)
{
powerLabel.Background = LEDONCOLOR;
powerLabel.Foreground = BLACKFONT;
}
else
{
powerLabel.Background = LEDOFFCOLOR;
powerLabel.Foreground = WHITEFONT;
}
}
}
public string UserControlName
{
get { return userControlNameLabel.Content.ToString(); }
set { userControlNameLabel.Content = value; }
}
public static DependencyProperty VinProperty = DependencyProperty.Register("Vin", typeof(byte), typeof(UserControl));
public static DependencyProperty PowerProperty = DependencyProperty.Register("Power", typeof(int), typeof(UserControl));
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here is the Observable Collection
namespace Test
{
class userData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public String name { get; private set; }
private byte voltageIn;
public byte VoltageIn
{
get
{
return voltageIn;
}
set
{
if (value != this.voltageIn)
{
this.voltageIn = value;
NotifyPropertyChanged();
}
}
}
private int power;
public int Power
{
get
{
return power;
}
set
{
if (value != this.power)
{
this.power = value;
NotifyPropertyChanged();
}
}
}
public userData(string name, byte voltageIn, int power)
{
this.name = name;
this.voltageIn = voltageIn;
this.power = power;
}
public userData(string name)
{
this.name = name;
this.voltageIn = 0x00;
this.power = 0;
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here is my Main window
<Window x:Class="Test.MainWindow"
xmlns:src="clr-namespace:Test"
xmlns:local="clr-namespace:Test"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" >
<Grid>
<Grid>
<Label Content="HW 0"/>
<src:UserControl1 x:Name="hw0unit1" Vin="{Binding Path=_userData[0].VoltageIn, Mode=OneWay}" />
<src:UserControl1 x:Name="hw0unit2"/>
</Grid>
<Grid>
<Label Content="HW 1"/>
<src:UserControl1 x:Name="hw1unit1"/>
<src:UserControl1 x:Name="hw1unit2"/>
</Grid>
</Grid>
</Window>
And my main program
namespace Test
{
public partial class MainWindow : Window
{
DispatcherTimer hwScan;
UserControl1[] userCntrl = new UserControl1[4];
byte[] vinDisplay = new byte[4];
int[] powerDisplay = new int[4];
ObservableCollection<userData> _userData;
// Main Routine
public MainWindow()
{
InitializeComponent();
// Create dispatch timer to call hwTimer_Tick
...
// Save off UserControls for all units into Array for easier access
userCntrl[0] = hw0unit1;
userCntrl[1] = hw0unit2;
userCntrl[2] = hw1unit1;
userCntrl[3] = hw1unit2;
// Init userData arrays
_userData = new ObservableCollection<userData>();
for (int i = 0; i < Properties.Settings.Default.unit.Count; i++)
_userData.Add(new userData(Properties.Settings.Default.unit[i]));
}
private void hwTimer_Tick(object sender, EventArgs e)
{
// Hardcode data for testing
vinDisplay[i] = a hex constant
powerDisplay[i] = a hex constant
for (int i=0; i<4; i++)
{
_userData[i].VoltageIn = vinDisplay[i];
_userData[i].Power = powerDisplay[i];
}
}
}
}

Set default value in WPF ComboBox

I am using ComboBox ItemsSource property binding to display items from a List to combo box.
Following is the code:
<ComboBox x:Name="Cmb_Tax" ItemsSource="{Binding TaxList}"
DisplayMemberPath="ChargeName" SelectedItem="{Binding
SelectedTax,UpdateSourceTrigger=PropertyChanged}" IsEditable="True"
IsTextSearchEnabled="True" SelectionChanged="Cmb_Tax_SelectionChanged"/>
Classes.Charges _selected_tax = new Classes.Charges();
public Classes.Charges SelectedTax
{
get
{
return _selected_tax;
}
set
{
_selected_tax = value;
}
}
List<Classes.Charges> _taxlist = new List<Classes.Charges>();
public List<Classes.Charges> TaxList
{
get
{
return _taxlist;
}
set
{
_taxlist = value;
OnPropertyChanged("TaxList");
}
}
It displays the items in the combo box correctly.
There is a particular item in TaxList "No Tax" which I want to be selected by default in the combo box. This item can be present at any index in the list (Not necessary first or last item of the list).
I am trying to use the following code to set the selected index property of combo box, but sadly its not working.
TaxList = Classes.Charges.GetChargeList("Tax");
Cmb_Tax.DataContext = this;
int i = TaxList.FindIndex(x => x.ChargeName == tax_name);
Cmb_Tax.SelectedIndex = i;
The Method FindIndex() returns the index of the "No Tax" correctly but when I try assigning it to SelectedIndex of combo the SelectedIndex doesn't change. It stays at -1.
Update1
private void Cmb_Tax_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show(SelectedTax.ChargeName);
}
Update2
Updated the code as per suggested by #ElectricRouge
<ComboBox x:Name="Cmb_Tax" ItemsSource="{Binding TaxList, Mode=TwoWay}"
DisplayMemberPath="ChargeName" SelectedItem="{Binding SelectedTax,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
IsEditable="True" IsTextSearchEnabled="True"
SelectionChanged="Cmb_Tax_SelectionChanged"/>
Classes.Charges _selected_tax = new Classes.Charges();
public Classes.Charges SelectedTax
{
get
{
return _selected_tax;
}
set
{
_selected_tax = value;
OnPropertyChanged("SelectedTax");
}
}
ObservableCollection<Classes.Charges> _taxlist = new ObservableCollection<Classes.Charges>();
public ObservableCollection<Classes.Charges> TaxList
{
get
{
return _taxlist;
}
set
{
_taxlist = value;
OnPropertyChanged("TaxList");
}
}
public void Load_Tax(string tax_name = null, Classes.Charges selected_tax = null)
{
TaxList = Classes.Charges.GetParticularChargeList("Tax");
Cmb_Tax.DataContext = this;
//Cmb_Tax.SelectedValue = tax_name;
SelectedTax = selected_tax;
//int i = TaxList.FindIndex(x => x.ChargeName == tax_name);
//Cmb_Tax.SelectedIndex = i;
}
Any idea why this must be happening?
Also please suggest any other approach to display default in combo box.
Here's a working sample:
Viewmodel:
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
this.DataContext = vm;
this.Loaded += (o,e) => vm.LoadData();
}
public class ViewModel : INotifyPropertyChanged
{
private IList<Charges> taxList;
public ICollectionView TaxList { get; private set; }
public void LoadData()
{
taxList = Charges.GetChargeList("taxes");
TaxList = CollectionViewSource.GetDefaultView(taxList);
RaisePropertyChanged("TaxList");
TaxList.CurrentChanged += TaxList_CurrentChanged;
var noTax = taxList.FirstOrDefault(c => c.ChargeName == "No Tax");
TaxList.MoveCurrentTo(noTax);
}
void TaxList_CurrentChanged(object sender, EventArgs e)
{
var currentCharge = TaxList.CurrentItem as Charges;
if(currentCharge != null)
MessageBox.Show(currentCharge.ChargeName);
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
View:
<ComboBox x:Name="cboTaxList"
ItemsSource="{Binding TaxList}"
DisplayMemberPath="ChargeName"
IsSynchronizedWithCurrentItem="True" />
List does not implement INotifyCollectionChanged make it ObservableCollection
ObservableCollection<Classes.Charges> _taxlist = new ObservableCollection<Classes.Charges>();
public ObservableCollection<Classes.Charges> TaxList
{
get
{
return _taxlist;
}
set
{
_taxlist = value;
OnPropertyChanged("TaxList");
}
}
And try setting the Mode=TwoWay
SelectedItem="{Binding SelectedTax,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"

refreshing Value Converter on INotifyPropertyChanged

I know there are some similar topics here but I couldn't get any answer from them.
I have to update background of a grid to either an image or a colour in my Windows Phone 7 app. I do this using my value converter , it works fine but I'd have to reload the collection so it updates the colour or image.
<Grid Background="{Binding Converter={StaticResource ImageConverter}}" Width="125" Height="125" Margin="6">
The converter receives the object then gets the color and image from it, here is the converter
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
People myC = value as People;
string myImage = myC.Image;
object result = myC.TileColor;
if (myImage != null)
{
BitmapImage bi = new BitmapImage();
bi.CreateOptions = BitmapCreateOptions.BackgroundCreation;
ImageBrush imageBrush = new ImageBrush();
using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
if (myIsolatedStorage.FileExists(myImage))
{
using (
IsolatedStorageFileStream fileStream = myIsolatedStorage.OpenFile(myImage, FileMode.Open,
FileAccess.Read))
{
bi.SetSource(fileStream);
imageBrush.ImageSource = bi;
}
}
else
{
return result;
}
}
return imageBrush;
}
else
{
return result;
}
}
I need to somehow update/refresh the grid tag or the value converter so that it can show the latest changes !
EDIT
ADDED SOME MORE CODE
The model :
[Table]
public class People : INotifyPropertyChanged, INotifyPropertyChanging
{
private int _peopleId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int PeopleId
{
get { return _peopleId; }
set
{
if (_peopleId != value)
{
NotifyPropertyChanging("PeopleId");
_peopleId = value;
NotifyPropertyChanged("PeopleId");
}
}
}
private string _peopleName;
[Column]
public string PeopleName
{
get { return _peopleName; }
set
{
if (_peopleName != value)
{
NotifyPropertyChanging("PeopleName");
_peopleName = value;
NotifyPropertyChanged("PeopleName");
}
}
}
private string _tileColor;
[Column]
public string TileColor
{
get { return _tileColor; }
set
{
if (_tileColor != value)
{
NotifyPropertyChanging("TileColor");
_tileColor = value;
NotifyPropertyChanged("TileColor");
}
}
}
private string _image;
[Column]
public string Image
{
get { return _image; }
set
{
if (_image != value)
{
NotifyPropertyChanging("Image");
_image = value;
NotifyPropertyChanged("Image");
}
}
}
[Column]
internal int _groupId;
private EntityRef<Groups> _group;
[Association(Storage = "_group", ThisKey = "_groupId", OtherKey = "Id", IsForeignKey = true)]
public Groups Group
{
get { return _group.Entity; }
set
{
NotifyPropertyChanging("Group");
_group.Entity = value;
if (value != null)
{
_groupId = value.Id;
}
NotifyPropertyChanging("Group");
}
}
[Column(IsVersion = true)]
private Binary _version;
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
ViewModel :
public class PeopleViewModel : INotifyPropertyChanged
{
private PeopleDataContext PeopleDB;
// Class constructor, create the data context object.
public PeopleViewModel(string PeopleDBConnectionString)
{
PeopleDB = new PeopleDataContext(PeopleDBConnectionString);
}
private ObservableCollection<People> _allPeople;
public ObservableCollection<People> AllPeople
{
get { return _allPeople; }
set
{
_allPeople = value;
NotifyPropertyChanged("AllPeople");
}
}
public ObservableCollection<People> LoadPeople(int gid)
{
var PeopleInDB = from People in PeopleDB.People
where People._groupId == gid
select People;
AllPeople = new ObservableCollection<People>(PeopleInDB);
return AllPeople;
}
public void updatePeople(int cid, string cname, string image, string tilecol)
{
People getc = PeopleDB.People.Single(c => c.PeopleId == cid);
getc.PeopleName = cname;
getc.Image = image;
getc.TileColor = tilecol;
PeopleDB.SubmitChanges();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Application Page
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Margin="0,8,0,0" x:Name="Peoplelist" HorizontalAlignment="Center" BorderThickness="4" ItemsSource="{Binding AllPeople}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="{Binding Converter={StaticResource ImageConverter}}" Width="125" Height="125" Margin="6">
<TextBlock Name="name" Text="{Binding PeopleName}" VerticalAlignment="Center" HorizontalAlignment="Center" TextAlignment="Center" TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
Application Page code behind
public partial class PeopleList : PhoneApplicationPage
{
private int gid;
private bool firstRun;
public PeopleList()
{
InitializeComponent();
firstRun = true;
this.DataContext = App.ViewModel;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
gid = int.Parse(NavigationContext.QueryString["Id"]);
if (firstRun)
{
App.ViewModel.LoadPeople(gid);
firstRun = false;
}
}
}
Background="{Binding Converter={StaticResource ImageConverter}}" suggests you bind directly to People item (which is your problem when refreshing).
So, you should rearrange a bit. Make that a property of some other 'higher' data context instead.
How to rearrange things:
1) your 'model' (entity from db) should be different than your view-model. To avoid going into details, it solves lot of problems - e.g. like you're having. The People getters/setters are not normally overriden in that way (EF often uses reflection to deal w/ entities etc.).
So, make PeopleVM (for single People or PersonViewModel) - copy things in there - and make INotify in there - leave People just a pure entity/poco w/ automatic get/set.
2) Same for the PeopleViewModel - it's too tied-up to the Db (these are also design guidelines).
You shouldn't reuse the DbContext - don't save it - it's a 'one off' object (and cached inside) - so use using() to deal with and load/update on demand.
3) Replace People in your main VM with PersonViewModel. When you load from db, pump up into the PersonVM first - when you're saving the other way around. That's a tricky bit with MVVM, you often need to copy/duplicate - you could use some tool for that to automate or just make copy ctor-s or something.
Your ObservableCollection<People> AllPeople becomes ObservableCollection<PersonViewModel> AllPeople
4) XAML - your binding AllPeople, PeopleName is the same - but that points now to view-models (and Name to VM Name).
But you should bind your grid to something other than PersonViewModel (old People) - as that is hard to 'refresh' when inside the collection.
a) Make a new single property like ImageAndTileColor - and make sure it updates/notifies on / when any of the two properties change.
b) The other option is to use MultiBinding - and bind 2, 3 properties - one being the whole PersonViewModel like you have and plus those other two properties - e.g...
<Grid ...>
<Grid.Background>
<MultiBinding Converter="{StaticResource ImageConverter}" Mode="OneWay">
<MultiBinding.Bindings>
<Binding Path="Image" />
<Binding Path="TileColor" />
<Binding Path="" />
</MultiBinding.Bindings>
</MultiBinding>
</Grid.Background>
<TextBlock Name="name" Text="{Binding PeopleName}" ... />
</Grid>
That way you force the binding to refresh when any of the 3 changes - and you still have your full People in there (actually you could just use two as all you need is Image and TileColor).
5) Change your Converter to be IMultiValue... and to read multiple values sent in.
That's all :)
SHORT VERSION:
That was the proper way and certain to work (it depends on how/when you update Person properties etc.) - but you could try the short version first - just do the multi-binding part on the People model - and hope it'd work. If it doesn't you have to do all the above.
Windows Phone 7:
Since there is no MultiBinding...
- Use the workaround - it should be quite similar,
- Or go with the (a) above - bind grid to {Binding ImageAndTileColor, Converter...}. Make new property (you can do the same if you wish in the entity/model - just mark it as [NotMapped()]) which would be a 'composite' one.
http://www.thejoyofcode.com/MultiBinding_for_Silverlight_3.aspx
I got it (Thanks to NSGaga). I set his post as the answer, the following is what I did
First I needed to make the converter to receive PeopleId instead of the object itself
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int cid = (int)value;
People myC = App.ViewModel.getPerson(cid);
string myImage = myC.Image;
object result = myC.TileColor;
if (myImage != null)
{
BitmapImage bi = new BitmapImage();
bi.CreateOptions = BitmapCreateOptions.BackgroundCreation;
ImageBrush imageBrush = new ImageBrush();
using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
if (myIsolatedStorage.FileExists(myImage))
{
using (
IsolatedStorageFileStream fileStream = myIsolatedStorage.OpenFile(myImage, FileMode.Open,
FileAccess.Read))
{
bi.SetSource(fileStream);
imageBrush.ImageSource = bi;
}
}
else
{
return result;
}
}
return imageBrush;
}
else
{
return result;
}
}
Then I just had to add call NotifyPropertyChanged("PeopleId") whenever I update Image or TileColor like this
private string _tileColor;
[Column]
public string TileColor
{
get { return _tileColor; }
set
{
if (_tileColor != value)
{
NotifyPropertyChanging("TileColor");
_tileColor = value;
NotifyPropertyChanged("TileColor");
NotifyPropertyChanged("PeopleId");
}
}
}
private string _image;
[Column]
public string Image
{
get { return _image; }
set
{
if (_image != value)
{
NotifyPropertyChanging("Image");
_image = value;
NotifyPropertyChanged("Image");
NotifyPropertyChanged("PeopleId");
}
}
}
This forces the value converter to refresh :)

Categories

Resources