I have this two combobox bound to 2 differnet database table. First is bound to table with 1 column with animal class (example Reptiles, Mammal, Amphibian etc) and the other has two colums Animal names and its respective animal class (example lizard - Reptiles, Snake - Reptiles, Dog - Mammal, Frog- Amphibian). Now what I want is when I select item in combobox1 the combobox2 should have the respective list of animal matching the item selected in combobox 1 with the respective animal class of each animal.
Here is what I have done till now
<ComboBox Grid.Column="2" Grid.Row="4" Margin="3,1,3,1"
Name="comboBox1"
ItemsSource="{Binding Source={StaticResource NameListData}, Path=Animal}"
Selectionchanged="comboBox1_Selectionchanged" />
<ComboBox Grid.Column="2" Grid.Row="5" Margin="3,1,3,1"
Name="comboBox2"
ItemsSource="{Binding Source={StaticResource NameListData}, Path=Stream}" />
The selection_changed function
private static string aa;
private void comboBox1_Selectionchanged(object sender, SelectionchangedEventArgs e)
{
aA = comboBox1.SelectedItem.ToString();
}
public static string aA
{
get { return aa; }
set { aa = value; }
}
The Collections to which 2 comboboxes are bound to and are located in MainViewModel class
DataClasses1DataContext dc = new DataClasses1DataContext();
public List<string> Animal
{
get
{
List<string> facult = new List<string>();
foreach (var a in dc.Faculties)
{
facult.Add(a.Animal1.ToString());
}
return facult;
}
}
string selection;
public List<string> Stream
{
get
{
selection = Schooling.Pages.NewStudentGeneral.aA;
List<string> stream = new List<string>();
var q = from c in dc.Streams where c.faculty ==selection select c;
foreach (var b in q )
{
stream.Add(b.stream1);
}
return stream;
}
}
Here the selection string is not getting the value selected in the combobox1 because whenever i hardcode and specify the selection part like in the statement down below
1 var q = from c in dc.Streams where c.faculty =="Reptiles" select c;
will give the respective animals for the Reptiles.
I guess if the selection variable was getting the selected value of combobox1 my problem have been solved. Or am am doing all wrong here?? please help.
You need to somehow tell your second ComboBox that it needs to update its items, you can either do this through implementations of INotifyPropertyChanged/INotifyCollectionChanged or do it manually. Since you have an explicit event for the selection change in the first ComboBox you might as well update the second manually as well, this could be probably done by changing the handler as follows:
private void comboBox1_Selectionchanged(object sender, SelectionchangedEventArgs e)
{
aA = comboBox1.SelectedItem.ToString();
comboBox2.GetBindingExpression(ComboBox.ItemsSourceProperty).UpdateTarget();
}
Related
I opened the question here but we cannot come to the solution for my problem. I decided to create new question as we came to some assumptions and the former question does not refer to the real problem(we thought it is the problem with binding but as you will read on it is not).
In few words I have a ListView with data from list called jointList.
The list is doing well and it has all the data necessary. (I checked it)
On each row of the ListView I put a ToggleSwitch(in xaml) and then I try to do something with each of the switches.
Each switch should correspond to the data from the same row.
I created Toggled event that should apply to all toggleSwitches like this:
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
foreach (var product in jointList)
{
if (product.IsOn == true)
{
ToggleTest.Text = product.ProductId.ToString(); // this is for testing only, later I would do something with the data retrieved
ToggleTest.Visibility = Visibility.Visible;
}
else
{
ToggleTest.Visibility = Visibility.Collapsed;
}
}
}
But this is making only one toggleSwitch work. It's the switch that corresponds to the last added product to the list ( I am guessing that it is refering to the last Id). The other switches return nothing as if the method was not iterating through the list correctly or as if there was only one switch hooked up.
So, is it possible to get all switches up and running by using just one Toggled event as I attempt to do?
Here's a sample which shows one way.
In this example we have the following Product view model:
public class Product : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
So just a single Name-property.
Then we have MainPage where we create a collection of products:
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
{
var items = new ObservableCollection<Product>();
for (int i = 0; i < 9; i++)
{
items.Add(new Product($"item {i}"));
}
this.Items.ItemsSource = items;
}
And the XAML which creates the view:
<ListView Loaded="FrameworkElement_OnLoaded" x:Name="Items">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="RowContent" Text="{Binding Name}"/>
<ToggleSwitch x:Name="Toggle" Grid.Column="1" Toggled="Toggle_OnToggled"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The result:
Now we want to change the text when user toggles the switch. This is done in Toggle_OnToggled-event handler:
private void Toggle_OnToggled(object sender, RoutedEventArgs e)
{
var toggle = (ToggleSwitch) sender;
var dataContext = ((Grid)toggle.Parent).DataContext;
var dataItem = (Product) dataContext;
dataItem.Name = $"Toggled {toggle.IsOn}";
}
So after a few toggles:
Mikael Koskinen has delivered the answer to my problem.
Most of my code was correct and identical to his solution, apart from the last bit that is OnToggled event handler.
Here is the working andd correct handler:
private void Toggle_OnToggled(object sender, RoutedEventArgs e)
{
var toggle = (ToggleSwitch)sender;
var dataContext = ((Grid)toggle.Parent).DataContext;
var dataItem = (ScheduleList)dataContext;
ToggleTest.Text = dataItem.ProductId;
}
My previous version of handler didn't include the important bit, that is dataContext and dataItem.
It works like a charm now.
I have two comboboxes: Categories and Types. When my form is initially displayed, I am listing all categories and types that exist in the database. For both comboboxes, I manually insert row 0 to be of value “All” so that they don’t have to choose either if they don’t want to.
I have both comboboxes bound to ReactiveObjects so that if the user selects a Category, the Types combobox is automatically re-populated with a query to show only types relevant to the selected Category along with the row 0 added.
When the user selects a Category, it runs the query properly, returns the relevant Types, adds the row 0 properly and the combobox is populated correctly; however, on the XAML size it’s not selecting the row 0, and it adds the red outline around the combobox signifying an invalid selection was made.
If no choice is made for the Type combobox and the form is submitted, the correct value of 0 is passed. So while everything is working properly, the red box around the Types combobox is communicating to the user that they did something wrong and I cannot determine why the XAML isn’t picking up the selected values. I have run the code without it adding the row 0 and it still has the same behavior, i.e., the combobox is populated correctly, but no row is selected and the red outline appears.
XAML for comboboxes
<ComboBox
Grid.Row="3"
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Style="{StaticResource SimpleComboBox}"
ItemsSource="{Binding Categories}"
SelectedValue="{Binding SearchCriteria.CategoryID}"
SelectedValuePath="ComboValueID"
DisplayMemberPath="ComboDataValue"
/>
<TextBlock
Grid.Row="3"
Grid.Column="2"
Style="{StaticResource NormalTextNarrow}"
Text="Type" VerticalAlignment="Top"
/>
<ComboBox
Grid.Row="3"
Grid.Column="3"
Width="200"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Style="{StaticResource SimpleComboBox}"
ItemsSource="{Binding Types}"
SelectedValue="{Binding SearchCriteria.TypeId}"
SelectedValuePath="ComboValueID"
DisplayMemberPath="ComboDataValue"
/>
Relevant VM code
// Definition of SearchCriteria. ResourceItem is a ReactiveObject and
// all of the relevant properties watch for changes in values.
private ResourceItem searchCriteria;
public ResourceItem SearchCriteria
{
get { return searchCriteria; }
set { this.RaiseAndSetIfChanged(ref searchCriteria, value); }
}
// This all happens in my constructor
// Defining Row 0
var b = new GenericCombobox { ComboValueID = 0, ComboDataValue = "All" };
// Populating the comboboxes from the database
Categories = omr.GetKTValues("RES_CATEGORIES");
Types = omr.GetKTValuesRU("RES_TYPES");
// Adding the row 0
Categories.Insert(0, b);
Types.Insert(0, b);
// The form is displayed correctly at this point with the row 0 selected
Problem Code
// When the user picks a category, this is the method that is invoked:
private void categoryChanged()
{
if (SearchCriteria.CategoryID != 0)
{
Types = rr.GetCategoryTypes(SearchCriteria.CategoryID);
SearchCriteria.TypeId = 0;
}
}
// This runs correctly and returns the relevant Types
public List<GenericCombobox> GetCategoryTypes(int categoryId)
{
string sql = "res.usp_GetCategoryTypes";
var types = new List<GenericCombobox>();
SqlConnection sqlCn = DatabaseCommunication.OpenConnection();
using (SqlCommand cmd = new SqlCommand(sql, sqlCn))
{
// Omitting db stuff for brevity...
try
{
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
types.Add(new GenericCombobox
{
ComboValueID = (int)dr["TypeId"],
ComboDataValue = (string)dr["Type"],
IsSelected = false,
Description = (string)dr["Type"],
ComboDataCode = (string)dr["Type"]
});
}
// More db-stuff omitted
}
// Adding the row 0
var b = new GenericCombobox { ComboValueID = 0, ComboDataValue = "All", IsSelected = false, Description = "All", ComboDataCode = "All" };
types.Insert(0, b);
return types;
}
Update with Additional Code
// Object containing the TypeId property
public class ResourceItem : ReactiveObject, ISelectable
{
public int Id { get; set; }
public int? OriginalItemId { get; set; }
// ...many other properties...
private int typeId;
public int TypeId
{
get { return typeId; }
set { this.RaiseAndSetIfChanged(ref typeId, value); }
}
// ...and many more follow...
I've been able to reproduce the issue, and I've found a few silly things I can do that make it stop happening.
If I select an item in Types other than "All", then when I change the selection in Category, it selects "All" in Types.
It also works if, in categoryChanged(), I replace this line...
SearchCriteria.TypeId = 0;
with this one:
SearchCriteria = new ResourceItem() {
TypeId = 0,
CategoryID = SearchCriteria.CategoryID
};
If I have ResourceItem.TypeId.set raise PropertyChanged regardless of whether the value has changed or not, it again works correctly.
My hypothesis is that the SelectedItem in the Types combobox (which you aren't even using!) is not changing when the collection changes, because you're not telling it to update SelectedValue.
Setting SearchCriteria.TypeId = 0 is a no-op when SearchCriteria.TypeId is already equal to zero, because RaiseAndSetIfChanged() does just what the name says: It checks to see if the value really changed, and if it hasn't, it doesn't raise PropertyChanged.
SelectedValue happens by chance to be the same value as the new "All" item has, but the combobox doesn't care. It just knows that nobody told it to go find a new SelectedItem, and the old one it has is no good any more because it's not in ItemsSource.
So this works too:
private void categoryChanged()
{
if (SearchCriteria.CategoryID != 0)
{
Types = rr.GetCategoryTypes(SearchCriteria.CategoryID);
SearchCriteria.SelectedType = Types.FirstOrDefault();
//SearchCriteria.TypeId = 0;
//SearchCriteria = new ResourceItem() { TypeId = 0, CategoryID = SearchCriteria.CategoryID };
}
}
XAML:
<ComboBox
Grid.Row="3"
Grid.Column="3"
Width="200"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="{Binding Types}"
SelectedValue="{Binding SearchCriteria.TypeId}"
SelectedItem="{Binding SearchCriteria.SelectedType}"
SelectedValuePath="ComboValueID"
DisplayMemberPath="ComboDataValue"
/>
class ResourceItem
private GenericCombobox selectedType;
public GenericCombobox SelectedType
{
get { return selectedType; }
set { this.RaiseAndSetIfChanged(ref selectedType, value); }
}
I think your best bet is my option #2 above:
private void categoryChanged()
{
if (SearchCriteria.CategoryID != 0)
{
Types = rr.GetCategoryTypes(SearchCriteria.CategoryID);
SearchCriteria = new ResourceItem() {
TypeId = 0,
CategoryID = SearchCriteria.CategoryID
};
// Do you need to do this?
// SearchCriteria.PropertyChanged += SearchCriteria_PropertyChanged;
}
}
The potential problem here is that, in my testing code I called categoryChanged() from a PropertyChanged handler on SearchCriteria. If I create a new SearchCriteria, I need to make sure I handle that event on the new one.
Given that, maybe binding SelectedItem on the Types combobox is the best solution after all: It's the only one I can think of that doesn't require the viewmodel to do strange things to make up for misbehavior in the view that it really shouldn't be aware of.
I'm trying to use a ListBox to choose an entry and then display a picture belonging to this selected entry. But just at the beginning I got my first problem: filling the ListBox with binding is working, but if I click on one line in my running program, it doesn't select the line. I can just see the highlighted hover effect, but not select a line. Any ideas what my mistake could be?
This is my XAML:
<ListBox x:Name="entrySelection" ItemsSource="{Binding Path=entryItems}" HorizontalAlignment="Left" Height="335" Margin="428,349,0,0" VerticalAlignment="Top" Width="540" FontSize="24"/>
And in MainWindow.xaml.cs I'm filling the ListBox with entries:
private void fillEntrySelectionListBox()
{
//Fill listBox with entries for active user
DataContext = this;
entryItems = new ObservableCollection<ComboBoxItem>();
foreach (HistoryEntry h in activeUser.History)
{
var cbItem = new ComboBoxItem();
cbItem.Content = h.toString();
entryItems.Add(cbItem);
}
this.entrySelection.ItemsSource = entryItems;
labelEntrySelection.Text = "Einträge für: " + activeUser.Id;
//show image matching the selected entry
if (activeUser.History != null)
{
int index = entrySelection.SelectedIndex;
if (index != -1 && index < activeUser.History.Count)
{
this.entryImage.Source = activeUser.History[index].Image;
}
}
}
So I can see my ListBox correctly filled, but not select anything - so I can't go on with loading the picture matching the selected entry.
I'm still quite new to programming, so any help would be great :)
EDIT: If someone takes a look at this thread later: here's the - quite obvious -solution
XAML now looks like this
<ListBox x:Name="entrySelection" ItemsSource="{Binding Path=entryItems}" HorizontalAlignment="Left" Height="335" Margin="428,349,0,0" VerticalAlignment="Top" Width="540" FontFamily="Siemens sans" FontSize="24">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind to fill it:
//Fill listbox with entries for selected user
DataContext = this;
entryItems = new ObservableCollection<DataItem>();
foreach (HistoryEntry h in selectedUser.History)
{
var lbItem = new DataItem(h.toString());
entryItems.Add(lbItem);
}
this.entrySelection.ItemsSource = entryItems;
labelEntrySelection.Text = "Einträge für: " + selectedUser.Id;
And new Class DataItem:
class DataItem
{
private String text;
public DataItem(String s)
{
text = s;
}
public String Text
{
get
{
return text;
}
}
}
You are filling it with ComboBoxItem, which is not relevant to the ListBox, and also wrong by definition.
You need to have the ObservableCollection filled with data items.
Meaning, make a class that contains the data you want to store, and the ListBox will generate a ListBoxItem automatically per data item.
http://www.wpf-tutorial.com/list-controls/listbox-control/
I do have a number of combo boxes on a custom control as below
<Label Grid.Row="12" Grid.Column="0" Name="lblCombobox1">
Select value from Combobox1
</Label>
<ComboBox Grid.Row="12" Grid.Column="1" Name="cbxCombobox1"
SelectionChanged="cbxCostCentre_SelectionChanged" />
<Label Grid.Row="13" Grid.Column="0" Name="lblCombobox2">
Select value from Combobox2</Label>
<ComboBox Grid.Row="13" Grid.Column="1" Name="cbxCombobox2"/>
This custom control I’m using on a main window as below
<StackPanel Background="LightCyan">
<views:NewAccount HorizontalAlignment="Center"
Margin="30" FontSize="14"/>
I’d need to do populate combo boxes cascading to have a previous combo box selected value as a filter parameter for the next one.
It seems as the following code could do it. It’s doing the filtering providing the Combobox2 with a list of values. However, I’m probably missing something as the LINQ query with “where” clause provides a result that is different if I ran it with T-SQL. It’s very similar but a few more or less values in the Combobox2 that is different from T-SQL list.
using System.Linq;
namespace AccountsSetup.UserControls
{
public partial class NewAccount : UserControl
{
public NewAccount()
{
InitializeComponent();
using (SQL.DBDataContext db = new SQL.DBDataContext())
{
var allCombobox1s = from t in db.Table1
select t.Name;
cbxCombobox1.ItemsSource = allCombobox1s;
}
}
private void cbxCombobox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
using (SQL.DBDataContext dbs = new SQL.DBDataContext())
{
string value = "";
if (cbxCombobox1.SelectedIndex >= 0)
value = cbxCombobox1.SelectionBoxItem.ToString();
var allCombobox2s = (from t in dbs.View1
where t.Combobox1.Contains(value)
select t.Name).Distinct();
cbxCombobox2.ItemsSource = allCombobox2s;
}
I did try to change the Combobox2 into the following code. But, it’s the same result.
<ComboBox Grid.Row="13" Grid.Column="1" x:Name="cbxCombobox2"
ItemsSource="{Binding}" SelectedValue="{Binding ElementName=cbxCombobox1,
Path=SelectedItem.Name, Mode=OneWay}" />
Please, advise what corrections could be done in the code.
Thanks
You should compare the value of the SelectedItem property of the first ComboBox with the value of the Name column in the view.
Try this:
private void cbxCombobox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
using (SQL.DBDataContext dbs = new SQL.DBDataContext())
{
string value = cbxCombobox1.SelectedItem as string;
if (!string.IsNullOrEmpty(value))
{
var allCombobox2s = (from t in dbs.View1
where t.Name != null && t.Name.Contains(value)
select t.Name).Distinct().ToList();
cbxCombobox2.ItemsSource = allCombobox2s;
}
}
}
I'm trying to remove items/rows from a ListView but the difficulty is that I need to also pass in some delegate or fire some event or something, so when a person clicks a button to remove that row, my code handles some other logic, elsewhere (eg. remove the item from the DB or whatever).
I have a custom control I made:
public class SportsTeam : StackLayout { .. }
Inside this control, one of the elements is a ListView which lists all the people in a sporting team.
var viewModel = teamMembers.Select(x => new SportsTeamViewModel(x));
return new ListView
{
HasUnevenRows = true,
ItemSource = viewModel,
ItemTemplate = new DataTemplate(typeof(SportsTeamViewCell));
};
Inside the SportsTeamViewCell I have the following:
private Grid CreateContent()
{
var grid = new Grid();
// Setup row and column definitions.
// Add items to the Grid
grid.Children.Add(...);
var removeButton = RemoveButton;
grid.Children.Add(removeButton);
Grid.SetRowSpan(removeButton, 2);
return grid;
}
private Button RemoveButton
{
get
{
var button = new Button
{
Image = "Icons/remove.png"
};
return button;
}
}
From here, I don't know how to make it so that the button fires an event or some delete could be passed in via the constructor, so some custom logic is performed against the individual cell/row/item that is to be removed.
Here is what you could do :
This be my model class :
public class Item
{
public string ItemName { get; set; }
public string ItemDetails { get; set; }
}
And in my XAML or you can write this in code as well, bind to the Command Parameter of your Item template :
<Button Text="Delete" CommandParameter="{Binding ItemName}" Clicked="DeleteClicked"></Button>
Full Item Template will be like below :
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding ItemName}" HorizontalOptions="StartAndExpand" FontSize="30"></Label>
<Button Text="Delete" CommandParameter="{Binding ItemName}" Clicked="DeleteClicked">
</Button>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
And in you code file you can do this :
public void DeleteClicked(object sender, EventArgs e)
{
var item = (Xamarin.Forms.Button)sender;
Item listitem = (from itm in allItems
where itm.ItemName == item.CommandParameter.ToString()
select itm)
.FirstOrDefault<Item>();
allItems.Remove(listitem);
}
IMPORTANT : This would only delete the item from the bound collection. To delete it from the original list you need to use ObservableCollection
Here is the full source code of the explained scenario - Handling Child Control Event in Listview using XAMARIN.FORMS.
Also the Tutorial - How to handle Row selection and delete Button in Row For Custom ListView using Xamarin.Forms explain deletion from a listview as well.
I've found a similar approach and I want to share it. I filled the list with an ObservableCollection<MyObject>. Then I filled the CommandParameter with just CommandParameter="{Binding .}". So I got the whole Object back. Then you can just cast the CommandParameterto your Object and remove it from the ObservableCollection<MyObject> List
XAML:
CommandParameter="{Binding .}"
Filling my List:
savingExpensesCollection = new ObservableCollection<SavingsExpensesEntry> ();
savingExpensesCollection .Add (new SavingsExpensesEntry ("1000 mAh Akku", "Dampfgarten", new DateTime (635808692400000000), 8.95));
savingExpensesCollection .Add (new SavingsExpensesEntry ("Cool-Mint Aroma", "Dampfgarten", new DateTime (635808692400000000), 3.95));
savingExpensesCollection .Add (new SavingsExpensesEntry ("Basis", "Dampfgarten", new DateTime (635808692400000000), 13.65));
savingExpensesList.ItemsSource = savingExpenses;
EventHandler:
void OnDelete(object sender, EventArgs e)
{
var menuItem = ((MenuItem)sender);
SavingsExpensesEntry see ((SavingsExpensesEntry)menuItem.CommandParameter);
savingExpensesCollection .Remove (see);
}
I've using a MenuItem but it's the same approach with a Button
I just did using delete button
public void OnDelete(object sender, EventArgs e)
{
var mi = ((MenuItem)sender);
PhotoViewModel photo= ((photoViewModel)mi.CommandParameter);
photoModel.Remove(photo);
}