I want to be able to update a ComboBox within my Gird. I'm assuming I need some sort of event system.
I've bound it as follows:
<ComboBox Name="ScreenLocations" Grid.Row="1" Margin="0,0,0,175" ItemsSource="{Binding Path=CurrentPlayer.CurrentLocation.CurrentDirections}" DisplayMemberPath="Name" SelectedValuePath="Name" SelectedValue="{Binding Path= Location}"/>
my xaml.cs is as follows:
public partial class MainWindow : Window
{
GameSession _gameSession;
public MainWindow()
{
InitializeComponent();
_gameSession = new GameSession();
DataContext = _gameSession;
}
}
I want to be able to change the CurrentDirections property and to have it updated in the UI.
The class and properties I have it bound to is:
public class Location
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Quest[] AvailableQuests { get; set; }
public Monster[] LocationMonsters { get; set; }
public Location[] CurrentDirections { get; set; }
public Location(string name, string description, Quest[] availableQuests, int id)
{
Name = name;
Description = description;
AvailableQuests = availableQuests;
ID = id;
CurrentDirections = new Location[] { };
LocationMonsters = new Monster[] { };
AvailableQuests = new Quest[] { };
}
}
You just need to implement interface System.ComponentModel.INotifyPropertyChanged on class Location. This will oblige you to define a PropertyChanged event that interested parties (such as the bound ComboBox) can subscribe to in order to detect changes, and you can then reimplement CurrentDirections as follows, such that it notifies interested parties of the change via this event :
private Location[] currentDirections;
public Location[] CurrentDirections
{
get {return currentDirections;}
set {currentDirections = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("CurrentDirections"));}
}
For completeness you should consider implementing this interface on Player, and for the other properties of Location.
Related
I'm having trouble copying data from one ObservableCollection to another. I have an api call GetItemsAsync from http that puts the response into a model called ShipList.cs. Inside of ShipList.cs there is ShipCatalog[] ships. I have created a second model called HangarList.cs with HangarCatalog[] hangars. I have a page that displays the master list of ships (ShipsList) I want the user to select the ship (ShipList.name is bound to this particular ListVIew. I tried to use .Where() to filter ShipList to only the match to the selected item and copy that data to HangarCatalog. I'm getting Cannot convert GallogForms.Api.ShipCatalog to GallogForms.Api.HangarCatalog using the following code.
ViewModel
private ShipCatalog _selectedShip;
public ShipCatalog SelectedShip
{
get {return _selectedShip; }
set
{if (_selectedShip != value)
_selectedShip = value;
id = _selectedShip.id;
CopyShipData();
private async void CopyShipData()
{
var _container = Items.Where(s =>
s.name.FirstOrDefault().ToString() == id.ToString()).ToList();
foreach (var item in _container.Where(s =>
s.name.FirstOrDefault().ToString() == id.ToString()).ToList())
// var items = await _gallogClient.GetItemsAsync<ShipList>();
// foreach (var item in items.ships.Where(s =>
// s.name.FirstOrDefault().ToString() == id.ToString()).ToList())
{
Hangars.Clear();
Hangars.Add(item);
}
}
I haven't found any answer yet, and I've read plenty, that can address my situation. myShipsList is bound to a new model I've created in the API that perfectly mirrors ShipCatalog[].
I've also keep running across answers that suggest ListViewItem.Item or in my case SuggestedShipView.Items. .Items is not an option for my ListViews in the view model.
AddShipPage.xaml
<StackLayout Orientation="Vertical">
<SearchBar x:Name="HangarListView" Text="Add To Your Fleet!"
TextChanged="HangarList_TextChanged"
BackgroundColor="Azure"
/>
<Grid>
<ListView x:Name="SuggestedShipView"
ItemsSource="{Binding Items}"
SelectedItem="{Binding selectedShip}"
BackgroundColor="Silver">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
.....................
ShipList.cs (API Query)
[ApiPath("ships")]
public class ShipList : ApiQueryable
{
public ShipCatalog[] ships { get; set; }
}
public class ShipCatalog : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int id { get; set; }
public string name { get; set; }
public string uri { get; set; }
public int rsi_id { get; set; }
public string img { get; set; }
public string mfr { get; set; }
public string flyable { get; set; }
public string scu { get; set; }
public string value { get; set; }
public string bgcolor { get; set; }
public string color { get; set; }
public string role { get; set; }
public bool _isVisible { get; set; }
public bool IsVisible
{
get { return _isVisible; }
set
{
if (_isVisible != value)
{
_isVisible = value;
OnPropertyChanged();
}
}
}
}
}
HangarList mirrors ShipList perfectly with the exception it's named HangarList, public HangarCatalog[] hangars
And Finally, the query which populates ShipCatalog[]
AddShipViewModel
Items.Clear();
var items = await _gallogClient.GetItemsAsync<ShipList>();
foreach (var item in items.ships.ToList())
{
Items.Add(item);
}
No error messages per se, but I have not been able to structure a method to complete this task. If you would like to see the entire project to see more of what I have going on, http://github.com/dreamsforgotten/GallogMobile
when you tap or select an item in a ListView, the second parameter of the event handler will contain a reference to the item selected/tapped. You just need to cast it to the correct type. Then you can reference all of its properties as needed
private void SuggestedShipView_ItemTapped(object sender, ItemTappedEventArgs e)
{
// e.Item is the specific item selected
ShipCatalog ship = (ShipCatalog)e.Item;
// you can then use this ship object as the data source for your Hangar list/control,
// and/or add it to another List that is just the items the user has selected
}
I have the following view model:
public sealed class FileViewModel : AbstractPropNotifier
{
private string _path;
private CategoryViewModel _category;
public string Path
{
get
{
return _path;
}
set
{
_path = value;
OnPropertyChanged(nameof(Path));
OnPropertyChanged(nameof(Title));
}
}
public string Title => System.IO.Path.GetFileNameWithoutExtension(Path);
public CategoryViewModel Category
{
get
{
return _category;
}
set
{
_category = value;
OnPropertyChanged(nameof(Category));
}
}
}
and Category view model:
public sealed class CategoryViewModel : IEquatable<CategoryViewModel>
{
public string Title { get; set; }
public EMyEnum Value { get; set; }
public bool Equals(CategoryViewModel other)
{
return Title.Equals(other.Title) && Value == other.Value;
}
public static CategoryViewModel From(EMyEnum eCat)
{
return new CategoryViewModel
{
Title = eCat.DescriptionAttr(),
Value = eCat
};
}
}
I set data context to my view model like:
public sealed class MainViewModel
{
public MainViewModel()
{
Files = new ObservableCollection<FileViewModel>();
Categories = GetCategories();
}
public ObservableCollection<FileViewModel> Files { get; set; }
public CategoryViewModel[] Categories { get; set; }
private CategoryViewModel[] GetCategories()
{
var enums = Enum.GetValues(typeof(EMyEnum));
var list = new List<CategoryViewModel>();
foreach (var en in enums)
{
EMyEnum cat = (EMyEnum)en;
list.Add(CategoryViewModel.From(cat));
}
return list.ToArray();
}
}
and
_model = new MainViewModel();
DataContext = _model;
and XAML:
<Window.Resources>
<CollectionViewSource x:Key="Categories" Source="{Binding Categories}"/>
</Window.Resources>
and in DataGrid element
<DataGridComboBoxColumn SelectedItemBinding="{Binding Category}" ItemsSource="{Binding Source={StaticResource Categories}}" Header="Category" Width="2*" DisplayMemberPath="Title"/>
The dropdown is populated correctly but cannot select automatically from dropdown a specific Category, means the Category column from Datagrid is empty.
I expected to select automatically from dropdown with correspondent Category...
Where is my mistake ? I tried with SelectedItemBinding and SelectedValueBinding but same issue. Nothing selected from dropdown.
To be clear:
For a file, I set a category but nothing is selected:
But dropdown has items:
There are probably different instances of CategoryViewModels in your MainViewModel compared to the ones in the FileViewModels.
You should either override Equals and GetHashCode in your CategoryViewModel class or make sure that you set the Category property of each FileViewModel to a CategoryViewModel that's actually present in the CategoryViewModel[] array of the MainViewModel.
I have a datagrid
<DataGrid Name="dtgFeatures" Height="100" Margin="10" ColumnWidth="*" CanUserAddRows="True" MouseLeftButtonUp="DtgFeatures_MouseLeftButtonUp"/>
which is binded to an observable collection
ObservableCollection<CfgPartPrograms> obcCfgPartPrograms = new ObservableCollection<CfgPartPrograms>();
with
[Serializable]
public class CfgPartPrograms
{
public CfgPartPrograms() { }
public string Group{ get; set;}
public string Description{ get; set;}
public string Filename{ get; set;}<------set with openfiledialog
public string Notes{ get; set;}
}
Now since I want to be able to insert the filename with an openfileDialog I have add this code:
private void DtgFeatures_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
int column = (sender as DataGrid).CurrentCell.Column.DisplayIndex;
if ( column == 2)
{
OpenFileDialog ofdPP = new OpenFileDialog();
if (ofdPP.ShowDialog() == true)
{
if (obcCfgPartPrograms.Count == 0)
obcCfgPartPrograms.Add(new CfgPartPrograms() { Filename = ofdPP.FileName });
else
obcCfgPartPrograms[selectedIndex].Filename = ofdPP.FileName;
dtgFeatures.ItemsSource = null;
dtgFeatures.ItemsSource = obcCfgPartPrograms;
}
}
the problem is that when I set the filename the observable collection has not been updated yet.
I'll explain that with images:
So I have added aaaa and bbb now I want to force the filename with the code above but when I do that the bind action has not been done yet on the observable collection so that aaaa and bbbb are not present.
In short how to tell the binded datagrid to update the binding??
Thanks in advance
Patrick
Your CfgPartPrograms class should implement the INotifyPropertyChanged interface and raise the PropertyChanged event whenever a data bound property is set to a new value: https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx
[Serializable]
public class CfgPartPrograms : System.ComponentModel.INotifyPropertyChanged
{
public CfgPartPrograms() { }
public string Group { get; set; }
public string Description { get; set; }
private string _fileName;
public string Filename
{
get { return _fileName; }
set { _fileName = value; NotifyPropertyChanged(); }
}
public string Notes { get; set; }
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
I found it here: I was missing the
dtgFeatures.CommitEdit(DataGridEditingUnit.Row, true);
command
I am trying to bind my ViewModel to my ComboBox. I have ViewModel class defined like this:
class ViewModel
{
public ViewModel()
{
this.Car= "VW";
}
public string Car{ get; set; }
}
I set this ViewModel as DataContext in Window_Load like:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new CarModel();
}
Then in my xaml, I do this to bind my ComboBox to this ViewModel. I want to show the "VW" as selected by default in my ComboBox:
<ComboBox Name="cbCar" SelectedItem="{Binding Car, UpdateSourceTrigger=PropertyChanged}">
<ComboBoxItem Tag="Mazda">Mazda</ComboBoxItem>
<ComboBoxItem Tag="VW">VW</ComboBoxItem>
<ComboBoxItem Tag="Audi">Audi</ComboBoxItem>
</ComboBox>
I have 2 questions:
How do I set default value selected in Combo Box to "VW" (once form loads, it should show "VW" in combo box).
Instead of setting ComboBoxItems like above in xaml, how to I set it in my ViewModel and then load these in ComboBox?
Thanks,
UPDATE:
So far, I manage to implement this but I get error as below in the ViewModel c-tor:
namespace MyData
{
class ViewModel
{
public ViewModel()
{
this.Make = "";
this.Year = 1;
this.DefaultCar = "VW"; //this is where I get error 'No string allowed'
}
public IEnumerable<Car> Cars
{
get
{
var cars = new Car[] { new Car{Model="Mazda"}, new Car{Model="VW"}, new Car{Model="Audi"} };
DefaultCar = cars.FirstOrDefault(car => car.Model == "VW");
}
}
public string Make { get; set; }
public int Year { get; set; }
public Car DefaultCar { get; set; }
}
class Car
{
public string Model { get; set; }
}
}
As you are going to implement MVVM it will be a lot better if you start to think in objects to represent Cars in your application:
public class ViewModel
{
public Car SelectedCar{ get; set; }
public ObservableCollection<Car> Cars{
get {
var cars = new ObservableCollection(YOUR_DATA_STORE.Cars.ToList());
SelectedCar = cars.FirstOrDefault(car=>car.Model == "VW");
return cars;
}
}
}
public class Car
{
public string Model {get;set;}
public string Make { get; set; }
public int Year { get; set; }
}
Your Xaml:
<ComboBox SelectedItem="{Binding SelectedCar}", ItemsSource="{Binding Cars}"
UpdateSourceTrigger=PropertyChanged}"/>
Default Value:
If you set viewModel.Car = "VW", then it should auto-select that item in the combo box.
For this to work you will need to either implement INotifyPropertyChanged or set Car before you set DataContext.
INotifyPropertyChanged implementation might look like:
class ViewModel : INotifyPropertyChanged
{
private string car;
public ViewModel()
{
this.Car = "VW";
this.Cars = new ObservableCollection<string>() { "Mazda", "VW", "Audi" };
}
public string Car
{
get
{
return this.car;
}
set
{
if (this.car != value)
{
this.car = value;
OnPropertyChanged();
}
}
}
public ObservableCollection<string> Cars { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2.
Bind ItemsSource and SelectedItem.
<ComboBox ItemsSource="{Binding Cars}"
SelectedItem="{Binding Car, Mode=TwoWay}">
</ComboBox>
You can also set ComboBox.ItemTemplate if your source or view is more complex than just displaying a string:
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
In the view model just add a list property:
public ObservableCollection<string> Cars { get; set; }
It doesn't have to be ObservableCollection but that type will auto-update the UI whenever you change the collection.
So I have tried a whole bunch of examples from google, but I cannot seem to give my treeview checkboxes....The only thing I can think of is that it is because my tree's ItemsSource is always changing
An example I was trying is: TreeView with Checkboxes
most noteable, the HierarchicalDataTemplate:
<HierarchicalDataTemplate
x:Key="CheckBoxItemTemplate"
ItemsSource="{Binding Children, Mode=OneTime}"
>
<StackPanel Orientation="Horizontal">
<!-- These elements are bound to a FooViewModel object. -->
<CheckBox
Focusable="False"
IsChecked="{Binding IsChecked}"
VerticalAlignment="Center"
/>
<ContentPresenter
Content="{Binding Name, Mode=OneTime}"
Margin="2,0"
/>
</StackPanel>
</HierarchicalDataTemplate>
and I set my tree's ItemTemplate to:
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
But I see no checkboxes. I think it is because in the example, the data is bound to Children...but in my example, I am always changing the itemsSource (moving from tree to tree, or editting it, and adding it back to the tree etc)...
Anyone know how to make it work?
Thanks!
*EDIT*
adding the treeview itemsSource I just go
tv_master.ItemsSource = t.TreeItems;
Where the t.TreeItems is a List which holds the top level nodes..
my treeview:
<my:SpecTreeView Margin="8,8,0,12"
x:Name="tv_local"
TreeViewItem.Selected="node_Selected" HorizontalAlignment="Left" Width="304"
x:FieldModifier="private" BorderBrush="Black">
The original class used by the person who wrote the checkbox code:
using System.Collections.Generic;
using System.ComponentModel;
namespace TreeViewWithCheckBoxes
{
public class FooViewModel : INotifyPropertyChanged
{
#region Data
bool? _isChecked = false;
FooViewModel _parent;
#endregion // Data
#region CreateFoos
public static List<FooViewModel> CreateFoos()
{
FooViewModel root = new FooViewModel("Weapons")
{
IsInitiallySelected = true,
Children =
{
new FooViewModel("Blades")
{
Children =
{
new FooViewModel("Dagger"),
new FooViewModel("Machete"),
new FooViewModel("Sword"),
}
},
new FooViewModel("Vehicles")
{
Children =
{
new FooViewModel("Apache Helicopter"),
new FooViewModel("Submarine"),
new FooViewModel("Tank"),
}
},
new FooViewModel("Guns")
{
Children =
{
new FooViewModel("AK 47"),
new FooViewModel("Beretta"),
new FooViewModel("Uzi"),
}
},
}
};
root.Initialize();
return new List<FooViewModel> { root };
}
FooViewModel(string name)
{
this.Name = name;
this.Children = new List<FooViewModel>();
}
void Initialize()
{
foreach (FooViewModel child in this.Children)
{
child._parent = this;
child.Initialize();
}
}
#endregion // CreateFoos
#region Properties
public List<FooViewModel> Children { get; private set; }
public bool IsInitiallySelected { get; private set; }
public string Name { get; private set; }
#region IsChecked
/// <summary>
/// Gets/sets the state of the associated UI toggle (ex. CheckBox).
/// The return value is calculated based on the check state of all
/// child FooViewModels. Setting this property to true or false
/// will set all children to the same check state, and setting it
/// to any value will cause the parent to verify its check state.
/// </summary>
public bool? IsChecked
{
get { return _isChecked; }
set { this.SetIsChecked(value, true, true); }
}
void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
{
if (value == _isChecked)
return;
_isChecked = value;
if (updateChildren && _isChecked.HasValue)
this.Children.ForEach(c => c.SetIsChecked(_isChecked, true, false));
if (updateParent && _parent != null)
_parent.VerifyCheckState();
this.OnPropertyChanged("IsChecked");
}
void VerifyCheckState()
{
bool? state = null;
for (int i = 0; i < this.Children.Count; ++i)
{
bool? current = this.Children[i].IsChecked;
if (i == 0)
{
state = current;
}
else if (state != current)
{
state = null;
break;
}
}
this.SetIsChecked(state, false, true);
}
#endregion // IsChecked
#endregion // Properties
#region INotifyPropertyChanged Members
void OnPropertyChanged(string prop)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
My TNode Class
public class TNode : TreeViewItem{
public int Level { get; set; }
public Boolean IsCheckedStr { get; set; }
public string Section { get; set; }
public string Note { get; set; }
public Boolean Locked { get; set; }
public string ID { get; set; }
public int Hierarchy { get; set; }
public string Type { get; set; }
public Boolean HasChildren { get; set; }
public string TextBlock { get; set; }
public Boolean ShowDetails { get; set; }
public List<TNode> Dependencies { get; set; }
public TNode(string id) {
ID = id;
Dependencies = new List<TNode>();
}
I just want to get the checkboxes to appear for now :(
Let me know if there is anything else you would like to see
Edit: DataTemplates are for data, TreeViewItems or subclasses thereof are not data, the template will be ignored. You should see an error regarding this in the Output-window of Visual Studio (which is helpful for debugging databinding issues of any sort).
See nothing wrong with this, you might want to post the code behind for the classes, and the TreeView instance declaration.
A common error is the lack of notifying interfaces. Also: Did you bind the ItemsSource of the TreeView itself to a root-list of items?