Binding a property with a different ItemSource WPF C# - c#

I have a list bound as ItemSource that contains two strings: Option 1 and option 2, I have 2 text boxes where I bind and display these two options. I also have two radio buttons next to the two textboxes. I want to bind these radiobuttons but every time I click on them nothing happens. I found out the reason for this, because now he is always trying to find the bool in my list whether the button is checked or not. Is there a way to set in the xaml code that I can access the bool property which is in my ViewModel?
ViewModel:
public class WortAuswahlViewModel : AntwortMoeglichkeitViewModel, IWortAuswahlViewModel
{
public ObservableCollection<AuswahlOptionen> m_auswahlOptionen;
public WortAuswahlViewModel(WortAuswahl wortAuswahl)
{
if (wortAuswahl?.Optionen == null)
{
return;
}
m_auswahlOptionen = new ObservableCollection<AuswahlOptionen>(wortAuswahl.Optionen);
}
public ObservableCollection<AuswahlOptionen> WortAuswahlOptionen
{
get
{
return m_auswahlOptionen;
}
set
{
if (m_auswahlOptionen != value)
{
m_auswahlOptionen = value;
OnPropertyChanged();
}
}
}
private bool m_isRadioButtonCheckedFirst;
public bool CheckButtonEins
{
get
{
return m_isRadioButtonCheckedFirst;
}
set
{
if (m_isRadioButtonCheckedFirst != value)
{
m_isRadioButtonCheckedFirst = value;
OnPropertyChanged();
}
}
}
private bool m_isRadioButtonCheckedSecond;
public bool CheckButtonZwei
{
get
{
return m_isRadioButtonCheckedSecond;
}
set
{
if (m_isRadioButtonCheckedSecond != value)
{
m_isRadioButtonCheckedSecond = value;
OnPropertyChanged();
}
}
}
}
}
XAML:
<Grid>
<StackPanel Grid.Row="1" Grid.Column="1" Margin="20">
<ItemsControl ItemsSource="{Binding WortAuswahlOptionen}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Viewbox Height="80" HorizontalAlignment="Left" VerticalAlignment="Top">
<StackPanel>
<RadioButton HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" IsChecked="{Binding CheckButtonEins}"/>
<DockPanel LastChildFill="True">
<TextBox Grid.Column="1" Margin="20, 0, 0, 0" x:Name="TXT_optionEinsLoesung" Text="{Binding OptionEins}" IsReadOnly="True"/>
</DockPanel>
<RadioButton HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" IsChecked ="{Binding CheckeButtonZwei}"/>
<DockPanel LastChildFill="True">
<TextBox Grid.Column="1" Margin="20, 0, 0, 0" x:Name="TXT_optionZweiLoesung" Text="{Binding OptionZwei}" IsReadOnly="True"/>
</DockPanel>
</StackPanel>
</Viewbox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>

DataContext of each ItemTemplate and ItemContainerStyle inside ItemsControl is automatically set to the corresponding element of the ItemsSource.
One way to redirect the DataContext to somewhere outside of the elements is to start the binding path from the DataContext of the root object of your Window.
So if your WortAuswahlViewModel is set to the DataContext of a Window, first you need to set the binding source to the Window using RelativeSource={RelativeSource AncestorType=Window} and then set path to Path=DataContext.CheckButtonEins
IsChecked="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CheckButtonEins}"
If your WortAuswahlViewModel is set to the DataContext of another UI element, replace Window with the type of that element.

Related

Inputted Items sometimes appear empty in Data grid

I have ItemsControl and a DataGrid in a WPF UserControl. this is how it looks like
when the "Add to card" button is pressed a ViewModel instance is added to ObservableCollection bound to the DataGrid.
<ItemsControl
ItemsSource="{Binding Meals}"
x:Name="MealList"
Margin="5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<components:MealCardCustomer
BorderBrush="OrangeRed"
BorderThickness="5px"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<ScrollViewer
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Disabled">
<DataGrid
HorizontalAlignment="Stretch"
IsReadOnly="True"
Background="Orange"
x:Name="OrderedMeals"
SelectionMode="Single"
ItemsSource="{Binding OrderedMeals, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
SelectedIndex="{Binding SelectedOrderedMeal, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}"
FontSize="26"
Grid.Column="0"
Grid.Row="0"
Margin="5"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name, Mode=OneWay}" Header="Name" />
<DataGridTextColumn Binding= "{Binding Price, Mode=OneWay}" Header="Price" />
<DataGridTextColumn Binding="{Binding Ingredients, Mode=OneWay}" Header="Ingredients" />
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
The problem is that sometimes when I add new items it appears like an empty column.
I tried to add a button which refreshes the data grid but when pressed it makes the all of the items blank.
Also I've wrapped the DataGrid in a ScrollViewer with a horizontal scroll which for some reason doesn't work.
That's the ViewModel of the View
private string? address;
public string? Address
{
get { return address; }
set { address = value; OnPropertyChaneg(nameof(Address)); }
}
private int selectedOrderedMeal = -1;
public int SelectedOrderedMeal
{
get { return selectedOrderedMeal; }
set { selectedOrderedMeal = value; OnPropertyChaneg(nameof(SelectedOrderedMeal)); }
}
private ObservableCollection<MealCardCustomerViewModel> meals;
public ObservableCollection<MealCardCustomerViewModel> Meals
{
get { return meals; }
set { meals = value; }
}
private ObservableCollection<MealCardCustomerViewModel> orderedMeals;
public ObservableCollection<MealCardCustomerViewModel> OrderedMeals
{
get { return orderedMeals; }
set { orderedMeals = value; OnPropertyChaneg(nameof(OrderedMeals)); }
}
public BaseCommand RemoveCommand { get; }
public BaseCommand FinishOrderCommand { get; }
public NavigateCommand NavigateToCustomerListOfOtders { get; }
public BaseCommand LoadMealsCommand { get; }
public CustomerOrderingViewModel(NavigationService customerListOfOrdersNavigationService, NavigationService helpNavigationService, IMealService mealService)
: base(helpNavigationService, mealService)
{
Meals = new ObservableCollection<MealCardCustomerViewModel>();
OrderedMeals = new ObservableCollection<MealCardCustomerViewModel>();
RemoveCommand = new RemoveMeal(this);
FinishOrderCommand = new FinishOrder(this, customerListOfOrdersNavigationService);
NavigateToCustomerListOfOtders = new NavigateCommand(customerListOfOrdersNavigationService);
LoadMealsCommand = new LoadMeals<CustomerOrderingViewModel>(this);
}
public static CustomerOrderingViewModel LoadViewModel(NavigationService customerListOfOrders, NavigationService helpNavigationService, IMealService mealService)
{
CustomerOrderingViewModel viewModel = new CustomerOrderingViewModel(customerListOfOrders, helpNavigationService, mealService);
viewModel.LoadMealsCommand.Execute(null);
return viewModel;
}
public override void LoadMealsList(List<Meal> meals)
{
Meals.Clear();
foreach (var meal in meals)
{
Meals.Add(new MealCardCustomerViewModel(meal,this));
}
}
That the Views which act like ItemTemplates for the ItemsControl
<Image
Source="{Binding MealImage, Converter ={StaticResource imageConverter}, Mode=TwoWay, TargetNullValue=DefaultImage}"
Stretch="Uniform"/>
<DockPanel
Grid.Row="1"
VerticalAlignment="Center"
Margin="5">
<TextBlock
FontSize="20"
Margin="5"
Text="Name :"/>
<TextBox
Text="{Binding Name,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontSize="20"
Margin="5"/>
</DockPanel>
<DockPanel
Grid.Row="2"
VerticalAlignment="Center"
Margin="5">
<TextBlock
FontSize="20"
Margin="5"
Text="Price :"/>
<TextBox
Text="{Binding Price, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat={}{0:f2}}"
FontSize="20"
Margin="5"/>
</DockPanel>
<DockPanel
Grid.Row="3"
VerticalAlignment="Center"
Margin="5">
<TextBlock
FontSize="20"
Margin="5"
Text="Ingredients:"/>
<TextBox
Text="{Binding Ingredients, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontSize="20"
Margin="5"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Visible"
HorizontalScrollBarVisibility="Visible"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
/>
</DockPanel>
<Button
Command="{Binding AddToCardCommand}"
Background="OrangeRed"
Grid.Row="4"
Margin="10 5 10 5"
Content="Add to cart"
FontSize="20"/>
and that's the command that adds the item to the ObservableCollection
private CustomerOrderingViewModel customerOrderingViewModel;
private MealCardCustomerViewModel mealCardCustomerViewModel;
public AddToCard(CustomerOrderingViewModel customerOrderingViewModel, MealCardCustomerViewModel mealCardCustomerViewModel)
{
this.customerOrderingViewModel = customerOrderingViewModel;
this.mealCardCustomerViewModel = mealCardCustomerViewModel;
}
public override void Execute(object? parameter)
{
customerOrderingViewModel.OrderedMeals.Add(mealCardCustomerViewModel);
}
The problem was with the images in the objects which are non existing right now and so they are null.
For some reason the null value cause infinite loop in the converter and so the view models could not load the properties of the entity but the collection could read that the count was changed thus displaying the empty rows.
The way you add items to the cart is not thread safe.
Immagine the AddToCart() being called wich will update your customerOrderingViewModel and mealCardCustomerViewModel. Then immagine that before Execute is called, some other thread changes customerOrderingViewModel or mealCardCustomerViewModel. This could result in Execute() adding the wrong (or a Null) meal to your order.
If that is the reason for your error, the following code shoud solve it:
public AddToCard(CustomerOrderingViewModel customerOrderingViewModel, MealCardCustomerViewModel mealCardCustomerViewModel)
{
customerOrderingViewModel.OrderedMeals.Add(mealCardCustomerViewModel);
this.customerOrderingViewModel = customerOrderingViewModel;
this.mealCardCustomerViewModel = mealCardCustomerViewModel;
}
If you dont need customerOrderingViewModel and mealCardCustomerViewModel in the class owning AddToCart(), you could even spare those variables completely.
Side note:
If you dont plan on changing the observable collections but only their content, you can simply declare them as public fiels and not as propertys. The setter of the propertys wil only be accessed when thwo whole ObservableCollection object is changed but not if its content is changed. PropertyChanged notifications for changes inside the ObservableCollection are handlled by the ObservableCollection implementation.

WPF Caliburn.Micro binding ViewModel property inside ItemTemplate

How can I bind Visibility of TooTip to ToolTipVisibility property which is in ViewModel?
I have MenuObject class,
public class MenuObject
{
public string Name { get; set; }
public string IconPath { get; set; }
}
MenuObjects collection, ToolTipVisibility property in ViewModel,
public class MainViewModel : Conductor<object>
{
private bool _toolTipVisibility;
private ObservableCollection<MenuObject> _menuItems;
public bool ToolTipVisibility
{
get { return _toolTipVisibility; }
set
{
_toolTipVisibility = value;
NotifyOfPropertyChange(() => ToolTipVisibility);
}
}
public ObservableCollection<MenuObject> MenuItems
{
get { return _menuItems; }
set
{
_menuItems = value;
NotifyOfPropertyChange(() => MenuItems);
}
}
public MainViewModel()
{
ToolTipVisibility = true;
}
public void ToggleVisibility()
{
ToolTipVisibility = !ToolTipVisibility;
}
}
and ListView binding with this collection
<ListView x:Name="MenuItems">
<ListView.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<BooleanToVisibilityConverter x:Key="b2vc"/>
</DataTemplate.Resources>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=IconPath}" Stretch="None" Margin="12,0,0,0"/>
<TextBlock Text="{Binding Path=Name}" Margin="25,0,0,0"/>
<StackPanel.ToolTip>
<ToolTip Content="{Binding Path=Name}"
Visibility="{Binding ..., Converter={StaticResource b2vc}}"/> <!--// How can i do this? //-->
</StackPanel.ToolTip>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Help me, please. Thank you! :)
UPDATE:
I tried many ways and still can't resovle it. But I found out something. If I put this
Visibility="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}, AncestorLevel=1},
Path=DataContext.ToolTipVisibility, Converter={StaticResource b2vc}}"
in tag StackPanel, then it works fine. But if i put same thing in tag ToolTip, it doesn't work. What am I missing?
Good question. By visible I'm going to assume that you mean "visible when mouse over", since that is what Visibility does for the ToolTip property in WPF.
I used the following view model, which is quite similar to yours. I left out all other bindings than visibility, for simplicty:
private bool _isToolTipVisible;
// The 'ToolTip.Visibility' will be bound to this property
public bool IsToolTipVisible
{
get => _isToolTipVisible;
set
{
_isToolTipVisible = value;
NotifyOfPropertyChange(nameof(IsToolTipVisible));
}
}
// This is just so that I am able to demonstrate the effect
public void ChangeToolTipVisibility()
{
IsToolTipVisible = !IsToolTipVisible;
}
For the view, I did pretty much what you are already doing, just adding a binding to IsToolTipVisible, to control the visibilty of the tool tip. The button I added, is just to be able to demonstrate the effect (in calls the method ChangeToolTipVisibility():
<Window.Resources>
<BooleanToVisibilityConverter x:Key="b2vc"/>
</Window.Resources>
<Grid Margin="100">
<StackPanel Orientation="Horizontal">
<StackPanel.ToolTip>
<ToolTip Content="Lorem ipsum" Visibility="{Binding IsToolTipVisible, Converter={StaticResource b2vc}}"/>
</StackPanel.ToolTip>
<TextBlock Text="Button text" Margin="25,0,0,0"/>
<Button x:Name="ChangeToolTipVisibility" Content="Change visiblity" />
</StackPanel>
</Grid>
That's all it takes. So you were pretty much there already, assuming that I understood your question correctly :-)
Finally, I found a solution. Here is the asnwer https://stackoverflow.com/a/26223802/13230344
This is for my case:
<ToolTip Content="{Binding Path=Name}"
Visibility="{Binding DataContext.ToolTipVisibility,
Source={x:Reference MenuItems}, Converter={StaticResource b2vc}}/>

Multiple checkbox in list and is check in wpf

I've got a question, I have a list of checkbox in combobox box and it looks like this:
<StackPanel Orientation="Vertical" DataContext="{Binding CandidateEntity}">
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding DataContext.SkillSetEntities, ElementName=CrudCandidate }"
IsEditable="True" IsReadOnly="True" Text="Umiejętności">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</StackPanel>
Now I also have a collection of skillset objects in binded item source (CandidateEntity.SkillSets), now how can I check those checkboxes that are in my collection of skillset objects?
I want to create a edition for CandidateEntity object in form and part of that edition is list of skillset that is represented in combobox.
EDIT:
I have solved problem by adding to skillset model prop:
private bool _isSelected = false;
[NotMapped]
public bool IsSelected
{
get
{
return this._isSelected;
}
set
{
_isSelected = value;
}
}
And then in view model:
private List<SkillSet> GetSkillSets()
{
var skillsetList = this._catalog.SkillSets.ToList();
var candidateSkillsetList = this.CandidateEntity.SkillSets.ToList();
foreach (SkillSet skillset in skillsetList)
{
foreach (SkillSet candidateSkillset in candidateSkillsetList)
{
if (skillset.id == candidateSkillset.id)
{
skillset.IsSelected = true;
}
}
}
return skillsetList;
}
and in checkbox in wpf:
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected}"/>
BUT I am pretty sure there must be easier way to handle that, is there?

WPF Listbox multiple selection with CheckBox in template

I have a listbox with a datatemplate that contains a checkbox and textblock. How do I get the multiselection working with the checkbox in the template so that selection is based on the isChecked property of the checkbox? The need I have is to have a dropdown with checkboxes that could cater for multiselection. This is what I have sofar. Please feel free to give suggestions on where I could better this code as this is the whole point.
XAML:
<DataTemplate x:Key="WorkCentreItem">
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding WorkCentre}" IsChecked="{Binding IsChecked}"/>
<TextBlock Text=" - "/>
<TextBlock Text="{Binding Description}"/>
</StackPanel>
</DataTemplate>
<telerik:RadListBox
SelectionMode="Multiple"
Grid.Row="2"
Grid.ColumnSpan="2"
Grid.RowSpan="1"
Margin="-1,20,0,3"
ItemsSource="{Binding SampleWorkCentres}"
ItemTemplate="{StaticResource WorkCentreItem}"
ScrollViewer.CanContentScroll="True"
SelectionChanged="RadListBox_SelectionChanged">
</telerik:RadListBox>
Model:
#region SampleWorkCentres
public const string SampleWorkCentresPropertyName = "SampleWorkCentres";
private ObservableCollection<BomWorkCentre> _sampleWorkCentres;
public ObservableCollection<BomWorkCentre> SampleWorkCentres
{
get
{
if (this._sampleWorkCentres == null)
{
using (SysproKitIssueEntities db = new SysproKitIssueEntities())
{
this._sampleWorkCentres = new ObservableCollection<BomWorkCentre>(db.BomWorkCentres.Select(x => x).Distinct().ToList());
}
}
return this._sampleWorkCentres;
}
set
{
if (this._sampleWorkCentres == value)
{
return;
}
this._sampleWorkCentres = value;
this.RaisePropertyChanged(SampleWorkCentresPropertyName);
}
}
#endregion
#region SelectedWorkCentres
public const string SelectedWorkCentresPropertyName = "SelectedWorkCentres";
private ObservableCollection<BomWorkCentre> _selectedWorkCentres = new ObservableCollection<BomWorkCentre>();
public ObservableCollection<BomWorkCentre> SelectedWorkCentres
{
get
{
return this._selectedWorkCentres;
}
set
{
if (this._selectedWorkCentres != value)
{
this._selectedWorkCentres = value;
}
this._sampleGridItems = null;
this.RaisePropertyChanged(SampleGridItemsPropertyName);
this.RaisePropertyChanged(SelectedWorkCentresPropertyName);
}
}
#endregion
You should data bind the Checkbox.IsChecked to the ListBoxItem.IsSelected Property using a RelativeSource Binding:
<CheckBox Content="{Binding WorkCentre}" IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" />

ListBox.SelectedItem and ListBoxItem.IsSelected get out of sync

I have a listbox defined in a WPF window as follows:
<ListBox ItemsSource="{Binding Values}" SelectedItem="{Binding SelectedValue}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="Black" Margin="5">
<StackPanel Margin="5">
<TextBlock FontWeight="Bold" Text="{Binding}"/>
<StackPanel Orientation="Horizontal">
<Label>Is Selected: </Label>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=IsSelected}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label>Selected Item:</Label>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=SelectedItem}"/>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And on the view model:
private string selectedValue;
private ObservableCollection<string> values;
public MainWindowViewModel()
{
this.values = new ObservableCollection<string>(new[] { "Fish", "Chips", "Peas" });
}
public ObservableCollection<string> Values
{
get
{
return this.values;
}
}
public string SelectedValue
{
get { return this.selectedValue; }
set
{
this.selectedValue = value;
if (value == "Peas")
{
this.SelectedValue = null;
}
this.OnPropertyChanged();
}
}
Each entry in the listbox displays its text and also a pair of textblocks indicating whether the listboxitem's IsSelected is set, and also what the current SelectedItem of the listbox is.
Everything works fine, until the "Peas" option is selected.
The view model has a piece of code which sets the viewmodel's SelectedValue to null in that instance, which in turn sets the listbox's SelectedItem property to null (overriding the fact that the user just clicked "Peas").
However, the ListBoxItem's IsSelected property for "Peas" does not get set to false even though the ListBox now has no selected item.
Any suggestions how to force the ListBoxItem to have IsSelected=false as well?
OK, I have found a workaround which seems to do the job. I have replaced the block
if (value == "Peas")
{
this.SelectedValue = null;
}
with
if (value == "Peas")
{
Dispatcher.CurrentDispatcher.BeginInvoke((Action)(() => this.SelectedValue = null));
}
essentially deferring the setting of the SelectedValue to null until after the block of code handling the user click has completed, and thus ensuring they don't interfere with each other.
I'll mark this as the accepted answer if nobody else has a more "elegant" solution to this!

Categories

Resources