Trouble binding to property in uwp MVVM project - c#

I am having a problem with binding from a view to a Viewmodel property.(UWP)
<AppBarToggleButton Label="Active" Icon="People"
IsChecked="{x:Bind ViewModel.IsStatusBtnChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Click="{x:Bind ViewModel.empStautsBtnClicked}"/>
private bool isStatusBtnChecked = true;
public bool IsStatusBtnChecked
{
get { return isStatusBtnChecked; }
set { Set(ref isStatusBtnChecked, value); }
}
When I try to get the value from a method to load combobox items the value is allways the default value
private List<string> depcombo;
public List<string> Depcombo
{
get { return depcombo; }
set
{
if (depcombo != value)
{
depcombo = value;
OnPropertyChanged("Depcombo");
}
}
}
public async void GetDepCombo()
{
List<string> _dep = new List<string>();
var data2 = await SqlServerDataService.GetAllEmployeesAsync();
var depResult = (from emp in EmpItems
where emp.Status == IsStatusBtnChecked
select emp.Department).Distinct();
foreach (var item in depResult)
{
if (item != null)
{
_dep.Add(item);
}
}
Depcombo = _dep;
}
When I load the data for Employyes it works fine
public async Task LoadDataAsync(MasterDetailsViewState viewState)
{
EmpItems.Clear();
var data = await SqlServerDataService.GetAllEmployeesAsync();
data.Where(em => em.Status == IsStatusBtnChecked).ToList().ForEach(p => EmpItems.Add(p));
if (viewState == MasterDetailsViewState.Both)
{
Selected = EmpItems.FirstOrDefault();
}
}
Some help will be much appreciated

When I try to get the value from a method to load combobox items the value is allways the default value
It's is confused that which is the combobox ItemsSource, if Depcombo is ComboBox ItemsSource, You have passed a new list instance to ItemsSource when you call GetDepCombo method.
Depcombo = _dep;
So, we need to set bind mode as OneWay(OneTime is default) that could response the object instance change.
<ComboBox
Margin="0,120,0,0"
ItemsSource="{x:Bind MainViewModel.Depcombo, Mode=OneWay}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
If EmpItems is ComboBox ItemsSource, and it is ObservableCollection type. When you call LoadDataAsync, EmpItems clear the items fist, then add the new items. And this processing does not change the EmpItems instance object. it could works in onetime mode.
<ComboBox Margin="0,120,0,0" ItemsSource="{x:Bind MainViewModel.EmpItems}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<AppBarToggleButton Label="Active" Icon="People"
x:Name="empStatusBtn"
IsChecked="{x:Bind ViewModel.IsStatusBtnChecked, Mode=TwoWay}"
Click="{x:Bind ViewModel.empStautsBtnClicked}"/>
public async void empStautsBtnClicked()
{
await LoadDataAsync(MasterDetailsViewState.Both);
}
Looks like the problem is when the view is reloaded. Is there a way to refresh the view without reloading. When reloading the value of
private bool isStatusBtnChecked = true;
public bool IsStatusBtnChecked
{
get { return isStatusBtnChecked; }
set
{
if (isStatusBtnChecked != value)
{
isStatusBtnChecked = value;
OnPropertyChanged("IsStatusBtnChecked");
}
}
}
is true but the button isChecked property is false;

Related

Error on binding table value to Combobox - wpf

I have a combobox which should bind the data dynamically from the database .
The source of the combobox is a observable collection.
Steps I followed:
Declared a combobox :
<ComboBox ItemsSource="{Binding populatecombobox.modeltogetusername }" Width="155" Margin="18,15,618,0"/>
Created a class to get the data from the database :
public class populatetab2combobox
{
public ObservableCollection<comboboxdata> modeltogetusername { get; set; }
public void getdatausinglinq()
{
using (Operations_Productivity_ToolEntities context = new Operations_Productivity_ToolEntities())
{
var a1 = from t1 in context.Test_ImportedAuditdata
select t1;
if (modeltogetusername == null)
modeltogetusername = new ObservableCollection<comboboxdata>();
foreach (var a in a1.GroupBy(x => x.username).Select(x => x.FirstOrDefault()))
{
modeltogetusername.Add(new comboboxdata
{
username = a.username
});
}
}
}
}
Instantiating the above mentioned class in viewmodel
public class ViewModel: INotifyPropertyChanged {
private populatetab2combobox _populatecombobox = new populatetab2combobox();
public populatetab2combobox populatecombobox {
get {
return _populatecombobox;
}
set {
if (value != _populatecombobox) {
_populatecombobox = value;
OnPropertyChanged("populatecombobox");
}
}
}
public ViewModel() {
_populatecombobox.getdatausinglinq();
}
}
The expected output is :
Ren1
Ren2
The actual output is
Namespace.Model.comboxdata
Namespace.Model.comboxdata
You are getting the output of the ToString() method and you are binding to instances of comboboxdata class and not the username inside of it.
You have 2 options.
First you can change your xaml to this notice how we bind to the property in the item template.
<ComboBox ItemsSource="{Binding populatecombobox.modeltogetusername }" Width="155" Margin="18,15,618,0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding username}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Second you can override the ToString() method on comboboxdata to return the username

View to ViewModel to Settings

Is it possible to refactor the currentDevices into a collection?
Basically, I have three comboboxes in which the selectedvalue is bound to currentDevices. then the currentDevices are taken from a settings file.
View
<ComboBox ItemsSource="{Binding availableDevices}"
SelectedValue="{Binding currentDevice1}">
</ComboBox>
<ComboBox ItemsSource="{Binding availableDevices}"
SelectedValue="{Binding currentDevice2}">
</ComboBox>
<ComboBox ItemsSource="{Binding availableDevices}"
SelectedValue="{Binding currentDevice3}">
</ComboBox>
ViewModel
public string currentDevice1 {
get
{
return SampleSettings.Default.Device1;
}
set
{
SampleSettings.Default.Device1 = value;
}
}
public string currentDevice2
{
get
{
return SampleSettings.Default.Device2;
}
set
{
SampleSettings.Default.Device2 = value;
}
}
public string currentDevice3
{
get
{
return SampleSettings.Default.Device3;
}
set
{
SampleSettings.Default.Device3 = value;
}
}
I'd write a DeviceOptionViewModel, and give the main viewmodel an ObservableCollection.
DeviceOptionViewModel.cs
public class DeviceOptionViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _currentDevice;
public String CurrentDevice {
get { return _currentDevice; }
set {
_currentDevice = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(CurrentDevice));
}
}
// Parent event assigns this to his own availableDevices
// when he creates this.
public IEnumerable AvailableDevices { get; set; }
}
Main VM:
public ObservableCollection<DeviceOptionViewModel>
CurrentDevices { get; private set; }
= new ObservableCollection<DeviceOptionViewModel>();
XAML:
<ItemsControl
ItemsSource="{Binding CurrentDevices}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- DataContext here is DeviceOptionViewModel. We gave it its
own reference to AvailableDevices to simplify binding. -->
<ComboBox
ItemsSource="{Binding AvailableDevices}"
SelectedValue="{Binding CurrentDevice}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Back to main viewmodel:
protected void PopulateCurrentDevices(IEnumerable<String> stringsFromWherever)
{
CurrentDevices.Clear();
foreach (var device in stringsFromWherever)
{
var dovm = new DeviceOptionViewModel() {
CurrentDevice = device,
AvailableDevices = this.availableDevices
};
dovm.PropertyChanged += DeviceOptionViewModel_PropertyChangedHandler;
CurrentDevices.Add(dovm);
}
}
protected void DeviceOptionViewModel_PropertyChangedHandler(object sender,
PropertyChangedEventArgs e)
{
var dopt = sender as DeviceOptionViewModel;
if (e.PropertyName == nameof(DeviceOptionViewModel.CurrentDevice))
{
// Do stuff
}
}
So you populate and repopulate CurrentDevices in your viewmodel as needed, and the UI will magically appear if all the notifications are done correctly.
If you create a new ObservableCollection and assign that to the CurrentDevices property, you'll need to raise PropertyChanged(nameof(CurrentDevices)) on the main viewmodel. I made the setter private to avoid having to implement that detail. If it's not a huge collection, may as well just Clear() and Add() on the same old instance.

WPF ComboBox with CheckBoxes display info about checked items?

Im trying to make a ComboBox that have checkboxes as items and based on what is checked display different things when the combobox is "closed".
The look Im trying achieve can be seen in the image.
Ideally I dont want the user to be able to select the text in the top ( in the image).
Is there a simple solution to this? I've seen solutions where one can display more information when all the items are shown by using DataTriggers to hide different nested controls, but that is not really what Im looking for.
Any ideas?
/Erik
Here is a way to achieve most of what you want using a ComboBox, except that the text can still be selected (using custom text only works when IsEditable is true). It is not editable though because of IsReadOnly="true".
View
<ComboBox
IsEditable="True"
IsReadOnly="True"
ItemsSource="{Binding Items}"
Text="{Binding Text}">
<ComboBox.ItemTemplate>
<DataTemplate
DataType="{x:Type local:Item}">
<CheckBox
Content="{Binding Name}"
IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Viewmodel
// ObservableObject is a custom base class that implements INotifyPropertyChanged
internal class MainWindowVM : ObservableObject
{
private ObservableCollection<Item> mItems;
private HashSet<Item> mCheckedItems;
public IEnumerable<Item> Items { get { return mItems; } }
public string Text
{
get { return _text; }
set { Set(ref _text, value); }
}
private string _text;
public MainWindowVM()
{
mItems = new ObservableCollection<Item>();
mCheckedItems = new HashSet<Item>();
mItems.CollectionChanged += Items_CollectionChanged;
// Adding test data
for (int i = 0; i < 10; ++i)
{
mItems.Add(new Item(string.Format("Item {0}", i.ToString("00"))));
}
}
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (Item item in e.OldItems)
{
item.PropertyChanged -= Item_PropertyChanged;
mCheckedItems.Remove(item);
}
}
if (e.NewItems != null)
{
foreach (Item item in e.NewItems)
{
item.PropertyChanged += Item_PropertyChanged;
if (item.IsChecked) mCheckedItems.Add(item);
}
}
UpdateText();
}
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsChecked")
{
Item item = (Item)sender;
if (item.IsChecked)
{
mCheckedItems.Add(item);
}
else
{
mCheckedItems.Remove(item);
}
UpdateText();
}
}
private void UpdateText()
{
switch (mCheckedItems.Count)
{
case 0:
Text = "<none>";
break;
case 1:
Text = mCheckedItems.First().Name;
break;
default:
Text = "<multiple>";
break;
}
}
}
// Test item class
// Test item class
internal class Item : ObservableObject
{
public string Name { get; private set; }
public bool IsChecked
{
get { return _isChecked; }
set { Set(ref _isChecked, value); }
}
private bool _isChecked;
public Item(string name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
}
If the selectable text is an issue, you may want to create a custom ComboBox control template (default example here). Alternatively, you could use something else instead of a ComboBox, and make it look like a ComboBox.
Screenshot of example:
Using a combination of the #Erik83 and #Xavier solution I still had the problem that selecting a ComboBoxItem in a location right from the CheckBox text closes the ComboBox-DropDown and shows the ToString() value of the ComboBoxItem as the CheckBox is not stretched to DropDown-Width. I solved it by adding
HorizontalContentAlignment="Stretch"
to the CheckBox and adding the ItemContainerStyle:
<ComboBox x:Name="combobox"
Background="White"
Padding="2"
Text="{Binding ElementName=DockPanelTemplateComboCheck, Path=ComboTextFilter}"
IsEditable="True"
IsReadOnly="True"
HorizontalAlignment="Stretch"
ItemsSource="{Binding ...}"
IsDropDownOpen="{Binding Path=DropOpen, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Eintrag}" HorizontalContentAlignment="Stretch" Checked="CheckBox_Checked_Unchecked" Unchecked="CheckBox_Checked_Unchecked"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
CodeBehind:
private string combotextfilter = "<No Selection>";
public string ComboTextFilter
{
get { return combotextfilter; }
set
{
if (value != null && value.IndexOf("ComboModel") != -1) return;
combotextfilter = value;
NotifyPropertyChanged(nameof(ComboTextFilter));
}
}
private void CheckBox_Checked_Unchecked(object sender, RoutedEventArgs e)
{
switch (((ObservableCollection<ComboModel>)combobox.ItemsSource).Count(x => x.IsChecked))
{
case 0:
ComboTextFilter = "<No Selection>";
break;
case 1:
ComboTextFilter = ((ObservableCollection<ComboModel>)combobox.ItemsSource).Where(x => x.IsChecked).First().Eintrag;
break;
default:
ComboTextFilter = ((ObservableCollection<ComboModel>)combobox.ItemsSource).Where(x => x.IsChecked).Select(x => x.Eintrag).Aggregate((i, j) => i + " | " + j);
//ComboTextFilter = "<Multiple Selected>";
break;
}
NotifyPropertyChanged(nameof(C_Foreground));
}
public bool DropOpen
{
get { return dropopen; }
set { dropopen = value; NotifyPropertyChanged(nameof(ComboTextFilter)); }
}
private bool dropopen = false;
#Xaviers answer works 99% of the way. However the user is able to accidently select a checkbox and then the ToString() of the checkbox is shown as the selected text. This can happen quite alot actually. I havent yet worked out why this happens, but I've found a way to prevent this.
Create a bool property that binds to the DropDownOpen property of the combobox and when the DropDownOpen has changed to false it means that the DropDown has just been closed and you might be facing the above problem.
So here you just raise the propertychanged event for the viewmodel and pass the property bound to the Text property of the combobox.

C# WPF - ComboBox, Values change but SelectedItem not

I know there are a lot questions about the SelectedItem and the ComboBox out there but it seems that they do not solve my problem.
I have a ComboBox where I bind a ObservableCollection of View Models and a TextBox which binds to the Description of the singe View models. I want to change the entries here... Everything works fine when i use the text box, the items will be changed in the ComboBox (even the selected one), but when i change the text in the code behind only the ComboBox List will be updated but not the current selected Value/Item.
My ComboBox looks like this at the moment:
<ComboBox ItemsSource="{Binding Sports, UpdateSourceTrigger=PropertyChanged}" SelectedValue="{Binding SelectedSport, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True" Width="365" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Height="30" Margin="5 5 5 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Description, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The used ViewModel looks like this:
public class SportViewModel : ViewModelBase
{
private ObservableCollection<DisciplineViewModel> _disciplinesProperty;
public Sport Model
{
get;
set;
}
public string Description
{
get { return Model.Description; }
set
{
if (Model.Description != value)
{
Model.Description = value;
RaisePropertyChanged(() => Description);
}
}
}
public ObservableCollection<DisciplineViewModel> Disciplines
{
get { return _disciplinesProperty; }
set
{
if (_disciplinesProperty != value)
{
_disciplinesProperty = value;
RaisePropertyChanged(() => Disciplines);
}
}
}
public SportViewModel(Sport source)
{
Model = source;
Disciplines = new ObservableCollection<DisciplineViewModel>();
foreach(Discipline d in Model.Disciplines)
{
Disciplines.Add(new DisciplineViewModel(d, this));
}
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is SportViewModel))
return false;
return ((SportViewModel)obj).Description == this.Description;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
and is used here:
class EditSportsDialogViewModel : ViewModelBase
{
...
public ObservableCollection<SportViewModel> Sports
{
get { return _sportsProperty; }
set
{
if (_sportsProperty != value)
{
_sportsProperty = value;
}
}
}
public SportViewModel SelectedSport
{
get { return _selectedSportPorperty; }
set
{
if (_selectedSportPorperty != value)
{
if (value != null)
{
Disciplines = value.Disciplines;
}
_selectedSportPorperty = value;
RaisePropertyChanged(() => SelectedSport);
}
}
}
private void SportRevertExecuted()
{
if (SelectedSport != null)
{
_context.Entry(SelectedSport.Model).Reload();
}
RaisePropertyChanged(() => SelectedSport.Description);
RaisePropertyChanged(() => SelectedSport);
RaisePropertyChanged(() => Sports);
RaisePropertyChanged();
}
}
The describtion is also changed by the one TextBox which works just fine
<TextBox Text="{Binding SelectedSport.Description, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" TextAlignment="Center" Margin="5" Width="410"/>
But when I call SportRevertExecuted() only the item in the list of the ComboBox will be changed but not the "Text" of the currently selected Item. Maybe someone can help me here, I've tried a lot until now, nothing seems to help.
Ok problem solved. Was quite stupid RaisePropertyChanged(() => SelectedSport.Description); doesn't work as i thought. I've introduced a Method UpdateView() in SportViewModel which raises the PropertyChanged Event in the right Object, now everything works just fine.

Trouble adding to Database

So I currently have a Datagrid, When the notes in the Datagrid are selected, It populates the Textbox Fields. That part works completely. I want to implement an "AddNewNote Button" the issue I currently have is that if an item was never selected, I get a nullreference. If an item was selected before hitting the button it works! But I need it to work in both scenarios.
private NoteDTO selectedNote;
public NoteDTO SelectedNote
{
get { return this.selectedNote; }
set
{
if (this.selectedNote == value)
return;
this.selectedNote = value;
this.OnPropertyChanged("SelectedNote");
}
}
xaml side
<DataGrid ItemsSource="{Binding Notes}" SelectedItem="{Binding SelectedNote}" />
<TextBox Text="{Binding SelectedNote.Subject}" />
<toolkit:RichTextBox Text="{Binding SelectedNote.Comments, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
public void AddNewNote()
{
var newNote = new Note();
newNote.Person_Id = PersonId;
newNote.Comments = SelectedNote.Comments;
newNote.Subject = SelectedNote.Subject;
using (var ctx = DB.Get())
{
ctx.Notes.Add(newNote);
ctx.SaveChanges();
}
this.OnPropertyChanged("newNote");
}
You're trying to bind to properties on SelectedNote, which is causes the exception when it's null:
<TextBox Text="{Binding SelectedNote.Subject}" />
<toolkit:RichTextBox Text="{Binding SelectedNote.Comments, ... }" />
You could handle this in the getter/setter, making sure that SelectedNote is never null:
get { return this.selectedNote ?? (this.selectedNote = new NoteDTO()); }
set
{
if (this.selectedNote == value)
return;
this.selectedNote = value ?? new NoteDTO(); // make sure it's never `null`
this.OnPropertyChanged("SelectedNote");
}

Categories

Resources