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.
Related
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.
I'm actually developping an UWP app who's reading Excel data, and display it on a form. Every Excel sheet are represented with RadioButton, and when the user click on a RadioButton, I update a ListView with the corresponding data.
Every item of the ListView have different data, and 2 checkboxes. This checkboxes can have the state "true" or "false", and if the user need to change the state, I want to modify his value
But the problem is, there is no event triggered with all the checkboxes when I check them. I tried to search and try to make my own template, but without success.
All the data are stored in a class :
public class REFERENCES
{
public int AI_ID { get; set; }
public int ID_poste { get; set; }
public string reference { get; set; }
public string designation { get; set; }
public bool avance { get; set; }
public bool jour { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public REFERENCES(int ai_id, int id_poste, string ref_, string des_, string avance_, string jour_)
{
AI_ID = ai_id;
ID_poste = id_poste;
reference = ref_;
designation = des_;
if(jour_ != null)
{
jour = true;
}
else
{
jour = false;
}
if (avance_ != null)
{
avance = true;
}
else
{
avance = false;
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And this is my ListView :
<ListView x:Name="ListViewData" SelectionMode="None" HorizontalAlignment="Center" Height="412" VerticalAlignment="Top" Width="650" Margin="0,218,0,0" >
<ListView.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Height="35" Margin="0,0,0,0" VerticalAlignment="Center" >
<TextBlock Text="AI_ID" Width="0" Visibility="Collapsed" />
<TextBlock Text="Désignations" FontSize="20" Width="300" Foreground="Blue" TextAlignment="Center" VerticalAlignment="Center" />
<TextBlock Text="Références" FontSize="20" Width="150" Foreground="Blue" TextAlignment="Center" VerticalAlignment="Center" />
<TextBlock Text="Avance" FontSize="20" Width="100" Foreground="Blue" TextAlignment="Center" VerticalAlignment="Center" />
<TextBlock Text="Jour" FontSize="20" Width="100" Foreground="Blue" TextAlignment="Center" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate x:DataType="ExcelLinkData:REFERENCES">
<StackPanel Orientation="Horizontal" >
<TextBlock Name="ItemAI_ID" Text="{x:Bind AI_ID}" Width="0" />
<TextBlock Name="ItemDesignation" Text="{x:Bind designation}" Width="300" FontSize="16" Height="55" VerticalAlignment="Center" HorizontalAlignment="Center" TextAlignment="Center"/>
<TextBlock Name="ItemReference" Text="{x:Bind reference}" Width="150" FontSize="16" Height="55" VerticalAlignment="Center" HorizontalAlignment="Center" TextAlignment="Center" />
<Grid Width="100" Height="55" VerticalAlignment="Center" HorizontalAlignment="Center">
<CheckBox Name="ItemAvance" IsChecked="{x:Bind avance}" Tag="{x:Bind AI_ID}" Checked="CHANGE_STATUS_REFERENCE"/>
</Grid>
<Grid Width="100" Height="55" VerticalAlignment="Center" HorizontalAlignment="Center">
<CheckBox Name="ItemJour" IsChecked="{x:Bind jour}" Tag="{x:Bind AI_ID}" Checked="CHANGE_STATUS_REFERENCE"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The method "CHANGE_STATUS_REFERENCE" is the method where I want to change the state in my class.
I tried different solutions, but I'm not an expert in UWP, so if anyone got an advice, I'll take it !
Thanks in advance for your time !
Guillaume
In UWP, default mode for Binding is OneTime. And when you change property from viewmodel then no event is triggered. By changing Binding to OneWay / TwoWay (depending on your usage), viewmodel will trigger the event.
Change
IsChecked="{x:Bind jour}"
To
IsChecked="{x:Bind jour Mode=TwoWay}"
Change
<CheckBox Name="ItemAvance" IsChecked="{x:Bind avance}" Tag="{x:Bind AI_ID}" Checked="CHANGE_STATUS_REFERENCE"/>
to
<CheckBox Name="ItemAvance" IsChecked="{x:Bind avance}" Tag="{x:Bind AI_ID}" Command="{Binding CHANGE_STATUS_REFERENCE}"/>
Add a "Flip Function" to your class:
private void Flip()
{
this.avance = !this.avance;
}
Set up a RelayCommand:
RelayCommand _flipCommand = new RelayCommand(this.Flip);
and Implement CHANGE_STATUS_REFERENCE as an ICommand like so:
public ICommand CHANGE_STATUS_REFERENCE
{
get
{
return _flipCommand;
}
}
How to show only last item of the list in itemssource binding ?
below is my current code.
xaml
<ItemsControl ItemsSource="{Binding UpgradeTicketStorage}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" Width="800">
<TextBlock Text="{Binding TicketTitle}" Style="{StaticResource TicketSelectionSubTitle}" TextAlignment="Left" />
<TextBlock Text="{Binding TicketDescription}" TextWrapping="Wrap" Style="{StaticResource TicketSelectionSubTitle2}" FontSize="19" TextAlignment="Left"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
from class binding i do have added 2 records which is ticketA and ticketB, how can i just display ticketB information ? instead of A and B
class
public class UpgradeTicketDescription : ViewModelBase
{
public string TicketTitle { get; set; }
public string TicketDescription { get; set; }
}
List<UpgradeTicketDescription> _UpgradeTicketStorage;
public List<UpgradeTicketDescription> UpgradeTicketStorage
{
get { return _UpgradeTicketStorage; }
set { _UpgradeTicketStorage = value; OnPropertyChanged("UpgradeTicketStorage"); }
}
UpgradeTicketStorage.Add(new UpgradeTicketDescription { TicketTitle = "TicketA", TicketDescription = "Observation DeckA (Single Ticket)"});
UpgradeTicketStorage.Add(new UpgradeTicketDescription { TicketTitle = "TicketB", TicketDescription = "Observation DeckB (Single Ticket)"});
If you want to bind to a specific item in a list you can go about it by creating a public variable you will bind to. Using what you have provided I've created an example that works for the last item in the list, all I did is create a new variable called LastItem and changed how the binding is in the project. This is just one of many ways of going about this.
xaml
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" Width="800">
<TextBlock Text="{Binding LastItem.TicketTitle}" TextAlignment="Left" />
<TextBlock Text="{Binding LastItem.TicketDescription}" TextWrapping="Wrap" FontSize="19" TextAlignment="Left"/>
</StackPanel>
class
public UpgradeTicketDescription LastItem
{
get { return UpgradeTicketStorage.Last(); }
}
This provides this output:
I am trying to learn about C# mvvm and wpf, so i decided to make a simple project(a book&readers managing application for a library) and now i am a little lost so i would really appreciate some help.
In my view i have a listview displaying the readers, and a bunch of textboxes bound to the selected item of the listview(name, adress,etc).
The listview is bound to an observable collection of readers created from a database table(Readers) with Linq2SQL.
Below the textboxes i have a few buttons (Clear/Add/Save/Delete).
The problems i am having are that the "selected item" of the listview, seems null if i try to execute some commands when i click the buttons.
when i click the clear button, if i have a breakpoint on the selected item(reader), it shows that it gets null/empty spaced, but the textboxes dont clear;
If in the Clear method in use the SelectedReader property, and assign empty spaces to its attributes, the selected item in the listview also clears(because its bound to Reader's FullName property)
i have other problems on adding a new reader, and saving changes, and i assume its because of wrong linq queries but i hope i can fix the current ones and move on afterwards.
What should i do?
ViewModel
public class MainWindowViewModel : ViewModelBase
{
private Reader selectedReader;
private ObservableCollection<Reader> readerList;
public MainWindowViewModel()
{
SelectedReader = new Reader()
{
FullName = "",
SerialNumber = "",
IdNumber = "",
Adress = "",
AltContactMethods = ""
};
BookDBDataContext rdb = new BookDBDataContext();
ReadersList = new ObservableCollection<Reader>(rdb.Readers);
AddR = new TblQryCommand(AddToDb);
EditR = new TblQryCommand(EditToDb);
DeleteR = new TblQryCommand(DeleteFromDb);
ClearR = new TblQryCommand(ClearReaderFields);
}
public TblQryCommand AddR { get; private set; }
public TblQryCommand EditR { get; private set; }
public TblQryCommand DeleteR { get; private set; }
public TblQryCommand ClearR { get; private set; }
//Reader List
public ObservableCollection<Reader> ReadersList
{
get { return readerList; }
set
{
if (readerList != value)
{
readerList = value;
RaisePropertyChanged();
}
}
}
public Reader SelectedReader
{
get { return selectedReader; }
set
{
if (selectedReader != value)
{
selectedReader = value;
RaisePropertyChanged();
}
}
}
public void AddToDb()
{
BookDBDataContext db = new BookDBDataContext();
Reader r = new Reader
{
FullName = SelectedReader.FullName,
SerialNumber = SelectedReader.SerialNumber,
IdNumber = SelectedReader.IdNumber,
Adress = SelectedReader.Adress,
AltContactMethods = SelectedReader.AltContactMethods
};
db.Readers.InsertOnSubmit(r);
db.Readers.Context.SubmitChanges();
}
public void DeleteFromDb()
{
}
public void EditToDb()
{
}
public void ClearReaderFields()
{
SelectedReader = new Reader
{
Id = 0,
FullName = string.Empty,
SerialNumber = string.Empty,
IdNumber = string.Empty,
Adress = string.Empty,
AltContactMethods = string.Empty
};
}
View:
<ListView Name="listviewReaders" ItemsSource="{Binding ReadersList}" SelectedItem="{Binding SelectedReader,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="140" Height="180" Margin="10,68,492,281">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FullName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBox x:Name="txtBxFullName" HorizontalAlignment="Left" Height="22" Margin="211,68,0,0" TextWrapping="Wrap" Text="{Binding ElementName=listviewReaders, Path=SelectedItem.FullName, Mode=TwoWay, UpdateSourceTrigger=Explicit}" VerticalAlignment="Top" Width="315"/>
<TextBox x:Name="txtBxSerialNumber" HorizontalAlignment="Left" Height="22" Margin="211,95,0,0" TextWrapping="Wrap" Text="{Binding ElementName=listviewReaders, Path=SelectedItem.SerialNumber, Mode=TwoWay, UpdateSourceTrigger=Explicit}" VerticalAlignment="Top" Width="315"/>
<TextBox x:Name="txtBxIdNumber" HorizontalAlignment="Left" Height="22" Margin="211,122,0,0" TextWrapping="Wrap" Text="{Binding ElementName=listviewReaders, Path=SelectedItem.IdNumber, Mode=TwoWay, UpdateSourceTrigger=Explicit}" VerticalAlignment="Top" Width="315"/>
<TextBox x:Name="txtBxAdress" HorizontalAlignment="Left" Height="22" Margin="211,149,0,0" TextWrapping="Wrap" Text="{Binding ElementName=listviewReaders, Path=SelectedItem.Adress, Mode=TwoWay, UpdateSourceTrigger=Explicit}" VerticalAlignment="Top" Width="315"/>
<TextBox x:Name="txtBxAltContactMethods" HorizontalAlignment="Left" Height="22" Margin="211,176,0,0" TextWrapping="Wrap" Text="{Binding ElementName=listviewReaders, Path=SelectedItem.AltContactMethods, Mode=TwoWay, UpdateSourceTrigger=Explicit}" VerticalAlignment="Top" Width="315"/>
<Button x:Name="btnReader_Clear" Content="Clear" Command="{Binding ClearR}" HorizontalAlignment="Left" Margin="211,228,0,0" VerticalAlignment="Top" Width="75"/>
<Button x:Name="btnReader_Save" Content="Save" Command="{Binding EditR}" HorizontalAlignment="Left" Margin="291,228,0,0" VerticalAlignment="Top" Width="75"/>
<Button x:Name="btnReader_Add" Content="Add New" Command="{Binding AddR}" HorizontalAlignment="Left" Margin="371,228,0,0" VerticalAlignment="Top" Width="75"/>
<Button x:Name="btnReader_Delete" Content="Delete" Command="{Binding DeleteR}" HorizontalAlignment="Left" Margin="451,228,0,0" VerticalAlignment="Top" Width="75"/>
Have you tried to set the Binding of the Text-Property of your textboxes to your ViewModel's SelectedReader-Property directly?
So instead of writing this:
<TextBox x:Name="txtBxFullName" Text="{Binding ElementName=listviewReaders, Path=SelectedItem.FullName, Mode=TwoWay, UpdateSourceTrigger=Explicit}" ... />
you would write:
<TextBox x:Name="txtBxFullName" Text="{Binding Path=SelectedReader.FullName, Mode=TwoWay, UpdateSourceTrigger=Explicit}" ... />
My problem:
I have a listbox with owners of dogs, and i have a listbox with dogs. I want to modify the dogs listbox itemtemplate as the following: DogName(textblock)+DogKind(textblock)+Owners(combobox).The first two was successful, but i cant add the existing owners to the combobox. If i give a name to my combobox like :
<ComboBox x:Name="mycombo" />
i cant see the mycombo variable in the c# code.
The XAML:
<Window x:Class="CodeFirst.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sajat="clr-namespace:CodeFirst"
Title="MainWindow" Height="557.638" Width="721.294"
>
<Grid x:Name="grid1">
<ListBox x:Name="listbox2" HorizontalAlignment="Left" Height="313" Margin="338,10,0,0" VerticalAlignment="Top" Width="250">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text=", "/>
<TextBlock Text="{Binding Path=Kind}"/>
<ComboBox />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
How can i give the itemsource to the combobox, or how can i reach to add the owners?
If you use the DataContext, you can set the Binding like this:
<ComboBox ItemsSource="{Binding Path=DataContext.MyItemsSource, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"></ComboBox>
First of all, in order to work with WPF or other XAML-based technologies, you must understand that
UI is not Data. Data is Data. UI is UI.
This means that you should not manipulate any ComboBox or any other UI elements in code, in order to populate them with data, but instead create a ViewModel and bind these objects to that.
In this example, the Window itself is used as ViewModel because it's a simple example, but you should consider moving all application logic to a separate class:
<Window x:Class="MiscSamples.UIisNotData"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UIisNotData" Height="300" Width="300">
<UniformGrid Rows="1" Columns="2">
<DockPanel>
<TextBlock Text="Owners:" DockPanel.Dock="Top" FontWeight="Bold" TextAlignment="Center" Margin="2"/>
<Button Content="Add" Width="80" DockPanel.Dock="Bottom" Margin="2" Click="AddOwner"/>
<ListBox ItemsSource="{Binding Owners}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}" x:Name="block"/>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Visibility="Collapsed" x:Name="box"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}}" Value="True">
<Setter TargetName="block" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="box" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
<DockPanel>
<TextBlock Text="Dogs:" DockPanel.Dock="Top" FontWeight="Bold" TextAlignment="Center" Margin="2"/>
<ListBox ItemsSource="{Binding Dogs}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<ComboBox ItemsSource="{Binding DataContext.Owners, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
SelectedItem="{Binding Owner}" DisplayMemberPath="Name"
DockPanel.Dock="Right" Width="100"/>
<TextBlock>
<Run Text="{Binding Name}"/>
<Run Text=", "/>
<Run Text="{Binding Kind}"/>
</TextBlock>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</UniformGrid>
</Window>
Code Behind (This code should be placed in a ViewModel):
public partial class UIisNotData : Window
{
public ObservableCollection<Owner> Owners { get; set; }
public ObservableCollection<string> Kinds { get; set; }
public ObservableCollection<Dog> Dogs { get; set; }
public UIisNotData()
{
InitializeComponent();
Owners = new ObservableCollection<Owner>
{
new Owner() {Name = "Jack"},
new Owner() {Name = "Mike"},
new Owner() {Name = "Kirk"},
new Owner() {Name = "John"},
};
Kinds = new ObservableCollection<string>
{
"Affenpinscher",
"Afghan Hound",
"Airedale Terrier",
"Akita"
//.. All the rest of dog Breeds taken from http://www.petmd.com/dog/breeds?breed_list=az#.UVsQKpPcmQo
};
Dogs = new ObservableCollection<Dog>
{
new Dog() {Name = "Bobby", Kind = Kinds[0], Owner = Owners[0]},
new Dog() {Name = "Fido", Kind = Kinds[1], Owner = Owners[1]},
new Dog() {Name = "Toby", Kind = Kinds[2], Owner = Owners[2]}
};
DataContext = this;
}
private void AddOwner(object sender, RoutedEventArgs e)
{
Owners.Add(new Owner(){Name = "New Owner"});
}
}
Data Model:
public class Owner : PropertyChangedBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public class Dog: PropertyChangedBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private Owner _owner;
public Owner Owner
{
get { return _owner; }
set
{
_owner = value;
OnPropertyChanged("Owner");
}
}
private string _kind;
public string Kind
{
get { return _kind; }
set
{
_kind = value;
OnPropertyChanged("Kind");
}
}
}
PropertyChangedBase Class:
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:
There are 3 important aspects you need to consider about this example:
I am in no way manipulating UI elements in code. That's completely unnecessary most of the time in WPF.
The classes from the Data Model implement INotifyPropertyChanged in order to support 2-way binding in WPF.
The Collections are of type ObservableCollection<T> in order to support automatic notification when elements are added/removed from the collection (in order to automatically update the ListBoxes, etc).
Another thing you may notice is that the XAML elements in my example have no specific size or Margin values. Things like Margin="338,10,0,0" is usually what you get from the Visual Studio designer and indicates a poorly structured layout. I recommend you look at the Layout elements in WPF (DockPanel, StackPanel, Grid, UniformGrid, WrapPanel, etc), and start coding the XAML yourself instead of using the designer. This will allow a much higher level of scalability and will also save you from the nuances of Fixed-position elements.