DataBinding in DataGrid within Tabs - c#

I have a main window with the following TabControl, inside the TabItem there is a DataGrid that should display the Businesses within a Section but can't resolve the Binding between the Section and the grid
Here is the code:
MainWindow.cs
public partial class MainWindow : Window {
static MainWindow () {
MainCatalog = Catalog.Instance;
}
public MainWindow () {
InitializeComponent();
DataContext = MainCatalog;
BusinessesGrid.DataContext = MainCatalog.FoodSection;
}
}
MainWindow.xaml
<TabControl x:Name="SectionControl" Grid.ColumnSpan="2" HorizontalAlignment="Left" Height="320"
VerticalAlignment="Top" Width="386">
<TabItem Header="Food Section">
<Grid Background="#FFE5E5E5" HorizontalAlignment="Left" Width="375">
<TextBox x:Name="SearchBox" HorizontalAlignment="Left" Height="23" Margin="15,13,0,0"
TextWrapping="Wrap" Text="Search" VerticalAlignment="Top" Width="140" />
<Button x:Name="SerchButton" Content="Search" HorizontalAlignment="Left" Margin="175,13,0,0"
VerticalAlignment="Top" Width="80" />
<Button x:Name="AddButton" Content="+" HorizontalAlignment="Left" Margin="295,13,0,0"
VerticalAlignment="Top" Width="20" />
<Button x:Name="RemoveButton" Content="-" HorizontalAlignment="Left" Margin="335,13,0,0"
VerticalAlignment="Top" Width="20" RenderTransformOrigin="0.4,0.682" />
<DataGrid x:Name="BusinessesGrid" HorizontalAlignment="Left" Height="220" Margin="15,53,0,0"
VerticalAlignment="Top" Width="340" ItemsSource="{Binding Catalog}"/>
</Grid>
</TabItem>
</TabControl>
Section Class:
public class Section : SearchBinaryTree < BinaryTreeNode < Business >,Business > {
public Section ( string name ) {
Name = name;
}
public Section ( BinaryTreeNode < Business > root, string name ) : base( root ) {
Name = name;
}
public string Name { get; set; }
public ObservableCollection < Business > Catalog {
get { return GetCatalog(); }
}
private ObservableCollection < Business > GetCatalog () {
var businesses = new ObservableCollection < Business >();
InOrder( businesses.Add );
return businesses;
}
}
Catalog Class has Four sections:
public class Catalog {
public Section FoodSection { get; }
public Section SchoolSection { get; }
public Section BeautySection { get; }
public Section FunSection { get; }
public static Catalog Instance { get; }
static Catalog () {
Instance = new Catalog();
}
private Catalog ( ) {
FoodSection = new Section("Food");
SchoolSection = new Section("School");
BeautySection = new Section("Beauty");
FunSection = new Section("Fun");
}
}
So the data grid has a ItemsSource attribute but it is not working, I'm new to WPF, does anyone has any ideas???

Delete this line of code as no need to set the DataContext of the DataGrid
BusinessesGrid.DataContext = MainCatalog.FoodSection;
as you've already set the DataContext of the page to MainCatelog you should just have to set the ItemsSource of BusinessGrid to FoodSection
<DataGrid x:Name="BusinessesGrid" HorizontalAlignment="Left" Height="220" Margin="15,53,0,0"
VerticalAlignment="Top" Width="340" ItemsSource="{Binding FoodSection}"/>

Related

C#/WPF Retrieve Textbox input and pass it as a parameter for a function

So in my WPF app, I am trying to figure out how to make this scenario as MVVM:
Input text in a TextBox;
On a button press, retrieve the input and pass it to a function that is for a command
I have tried doing something along the lines of this:
private string creatureName;
public string CreatureName
{
get {
return creatureName;
}
set
{
if(!string.Equals(creatureName, value))
{
creatureName = value;
OnPropertyChanged("CreatureName");
}
}
}
and textbox like this:
<TextBox x:Name="CreatureNameBox" Text="{Binding Path=CreatureName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" TextWrapping="NoWrap" FontFamily="Century Gothic" FontSize="16" Margin="150,0,150,16" MaxLines="1" Height="26" HorizontalAlignment="Stretch" VerticalAlignment="Center" Padding="5,3,5,3" MaxLength="100"/>
but it didn't work so I removed it.
Below is my code, if more code is needed let me know & I will update the ticket
TextBox:
<TextBox x:Name="CreatureNameBox" TextWrapping="NoWrap" FontFamily="Century Gothic" FontSize="16" Margin="150,0,150,16" MaxLines="1" Height="26" HorizontalAlignment="Stretch" VerticalAlignment="Center" Padding="5,3,5,3" MaxLength="100"/>
Button: Command is CreateNewCreature
<StackPanel>
<Button x:Name="CreateButton" Content="Create" FontWeight="Bold" FontFamily="Century Gothic" FontSize="18" Margin="10,3,10,0" Padding="0,5,0,5" Command="{Binding CreateNewCreature}" Height="34"/>
</StackPanel>
ViewModel: NewCreature() is where I want to pass the input value to as a string
namespace Creator.ViewModels
{
public class CreatureCreatorViewModel : ViewModelBase, INotifyPropertyChanged
{
public CreatureModel CreateNewCreature { get; set; }
public CreatureCreatorViewModel(NavigationStore navigationStore)
{
CreateNewCreature = new CreatureModel().NewCreature(*Retrieved TextBoxInput here*);
}
public CreatureCreatorViewModel()
{
}
}
}
Model:
namespace Creator.Models
{
public class CreatureModel : ISerializable
{
public string Name { get; set; }
public CreatureModel() { }
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Name", Name);
}
public CreatureModel NewCreature(string creatureName)
{
CreatureModel newCreature = new CreatureModel
{
Name = creatureName
};
using (FileStream fs = File.Open("[Censored Directory]/Data/TEST.5e", FileMode.Create))
{
Byte[] info = new UTF8Encoding(true).GetBytes("Name : " + newCreature.Name);
fs.Write(info);
}
return newCreature;
}
public CreatureModel(SerializationInfo info, StreamingContext context)
{
Name = (string)info.GetValue("Name", typeof(string));
}
}
}
For the simplest case.
ViewModel:
public class FunctionViewModel : BaseInpc
{
private int? _functionValue;
public int? FunctionValue { get => _functionValue; set => Set(ref _functionValue, value); }
private RelayCommand _functionCommand;
public RelayCommand FunctiontCommand => _functionCommand
?? (_functionCommand = new RelayCommand<string>(FunctionExecute));
private void FunctionExecute(string parameter)
{
//Some function is calculated here from received parameter.
//For example, just the length of the string.
FunctionValue = parameter?.Length;
}
}
XAML:
<UniformGrid DataContext="{DynamicResource viewModel}" Columns="2">
<FrameworkElement.Resources>
<local:FunctionViewModel x:Key="viewModel"/>
</FrameworkElement.Resources>
<TextBox x:Name="textBox" VerticalAlignment="Center" Margin="5" HorizontalContentAlignment="Center"/>
<Button Content="Calculate function" Padding="15 5" VerticalAlignment="Center"
Command="{Binding FunctiontCommand, Mode=OneWay}"
CommandParameter="{Binding Text, ElementName=textBox}"/>
<TextBlock Text="Function value:" HorizontalAlignment="Right" Margin="5"/>
<TextBox Text="{Binding FunctionValue}" IsReadOnly="True" VerticalAlignment="Top" Margin="5"
HorizontalContentAlignment="Center"/>
</UniformGrid>
The base classes from this answer were used: https://stackoverflow.com/questions/63174956/wpf-update-button-if-text-in-textbox-changes/63179286?r=SearchResults&s=1|42.4027#63179286

C# WPF Nested List in MVVM

I'd like to use a nested ObservableCollection in MVVM in order to add as many groups and users as possible. However, I don't know how to create/add a new user. I also don't know how to bind the new users to the XAML.
(NOTE: this time, I just need two persons.)
I'm new to C#, WPF and MVVM.
I'm learning MVVM referring to this site: https://riptutorial.com/wpf/example/992/basic-mvvm-example-using-wpf-and-csharp
I've been trying this since last week, but no luck.
I tried:
outerObservableCollection.Add(
new ObservableCollection<User>
{
{
FirstName = "Jane",
LastName = "June",
BirthDate = DateTime.Now.AddYears(-20)
}
}
);
... which ends up with the following error:
The name 'BirthDate' does not exist in the current context
(I guess the cause of this issue is that I didn't create a 'user' object, so 'user.BirthDate' is not accessible.)
Let me show the entire codes.
MainWindow.xaml:
<Window x:Class="MVVM_RIP_Tutorial.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MVVM_RIP_Tutorial"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="32.8"/>
<RowDefinition Height="28.8"/>
<RowDefinition Height="38*"/>
<RowDefinition Height="37*"/>
<RowDefinition Height="38*"/>
<RowDefinition Height="155*"/>
</Grid.RowDefinitions>
<!--1st Person-->
<TextBlock Grid.Column="1" Grid.Row="0" Margin="320.6,4,398.6,3.2" Text="{Binding FullName}" HorizontalAlignment="Center" FontWeight="Bold" Width="0"/>
<Label Grid.Column="0" Grid.Row="1" Margin="0,4.8,4.4,2.8" Content="First Name:" HorizontalAlignment="Right" Width="70"/>
<!-- UpdateSourceTrigger=PropertyChanged makes sure that changes in the TextBoxes are immediately applied to the model. -->
<TextBox Grid.Column="1" Grid.Row="1" Margin="3.6,4.8,0,1.8" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>
<Label Grid.Column="0" Grid.Row="2" Margin="0,5.2,4.4,1.6" Content="Last Name:" HorizontalAlignment="Right" Width="69"/>
<TextBox Grid.Column="1" Grid.Row="2" Margin="3.6,5.2,0,1.6" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>
<Label Grid.Column="0" Grid.Row="3" Margin="0,6.4,4.4,4.2" Content="Age:" HorizontalAlignment="Right" Grid.RowSpan="2" Width="33"/>
<TextBlock Grid.Column="1" Grid.Row="3" Margin="3.6,6.4,0,10.2" Text="{Binding Age}" HorizontalAlignment="Left" Grid.RowSpan="2" Width="0"/>
<!--2nd Person-->
<!--<TextBlock Grid.Column="1" Grid.Row="4" Margin="320.6,4,398.6,3.2" Text="{Binding FullName}" HorizontalAlignment="Center" FontWeight="Bold" Width="0"/>
<Label Grid.Column="0" Grid.Row="5" Margin="0,4.8,4.4,2.8" Content="First Name:" HorizontalAlignment="Right" Width="70"/>
--><!-- UpdateSourceTrigger=PropertyChanged makes sure that changes in the TextBoxes are immediately applied to the model. --><!--
<TextBox Grid.Column="1" Grid.Row="5" Margin="3.6,4.8,0,1.8" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>
<Label Grid.Column="0" Grid.Row="6" Margin="0,5.2,4.4,1.6" Content="Last Name:" HorizontalAlignment="Right" Width="69"/>
<TextBox Grid.Column="1" Grid.Row="6" Margin="3.6,5.2,0,1.6" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>
<Label Grid.Column="0" Grid.Row="7" Margin="0,6.4,4.4,4.2" Content="Age:" HorizontalAlignment="Right" Grid.RowSpan="2" Width="33"/>
<TextBlock Grid.Column="1" Grid.Row="7" Margin="3.6,6.4,0,10.2" Text="{Binding Age}" HorizontalAlignment="Left" Grid.RowSpan="2" Width="0"/>-->
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace MVVM_RIP_Tutorial
{
public partial class MainWindow : Window
{
private readonly MyViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MyViewModel();
// The DataContext serves as the starting point of Binding Paths
DataContext = _viewModel;
}
}
}
User.cs:
using System;
namespace MVVM_RIP_Tutorial
{
sealed class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
}
}
MyViewModel.cs:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace MVVM_RIP_Tutorial
{
// INotifyPropertyChanged notifies the View of property changes, so that Bindings are updated.
sealed class MyViewModel : INotifyPropertyChanged
{
private User user;
ObservableCollection<ObservableCollection<User>> outerObservableCollection
= new ObservableCollection<ObservableCollection<User>>();
//ObservableCollection<User> user = new ObservableCollection<User>();
public string FirstName
{
get { return user.FirstName; }
set
{
if (user.FirstName != value)
{
user.FirstName = value;
OnPropertyChange("FirstName");
// If the first name has changed, the FullName property needs to be udpated as well.
OnPropertyChange("FullName");
}
}
}
public string LastName
{
get { return user.LastName; }
set
{
if (user.LastName != value)
{
user.LastName = value;
OnPropertyChange("LastName");
// If the first name has changed, the FullName property needs to be udpated as well.
OnPropertyChange("FullName");
}
}
}
// This property is an example of how model properties can be presented differently to the View.
// In this case, we transform the birth date to the user's age, which is read only.
public int Age
{
get
{
DateTime today = DateTime.Today;
int age = today.Year - user.BirthDate.Year;
if (user.BirthDate > today.AddYears(-age)) age--;
return age;
}
}
// This property is just for display purposes and is a composition of existing data.
public string FullName
{
get { return FirstName + " " + LastName; }
}
public MyViewModel()
{
user = new User
{
FirstName = "John",
LastName = "Doe",
BirthDate = DateTime.Now.AddYears(-30)
};
//outerObservableCollection.Add(user);
//outerObservableCollection.Add(
// new ObservableCollection<User>
// {
// {
// FirstName = "Jane",
// LastName = "June",
// BirthDate = DateTime.Now.AddYears(-20)
// }
// }
//);
);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
... Please help me. Thank you in advance.
First of all, welcome to C#, WPF, and MVVM!
From your description it sounds like you would like to display a tree of users within groups... with that in mind, you could implement something like this to accomplish that goal:
Models
public class GroupModel
{
public GroupModel(uint id, string name)
{
Id = id;
Name = name;
}
public uint Id { get; }
public string Name { get; }
}
public class UserModel
{
public UserModel(uint id, string firstName, string surname, DateTime dateOfBirth)
{
Id = id;
FirstName = firstName;
Surname = surname;
DateOfBirth = dateOfBirth;
}
public uint Id { get; }
public string FirstName { get; }
public string Surname { get; }
public DateTime DateOfBirth { get; }
}
ViewModels
Base classes
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public abstract class ViewModelBase<TModel> : ViewModelBase
where TModel : class
{
private TModel _model;
public ViewModelBase(TModel model)
=> _model = model;
/*
* There is a design choice here to allow the model
* to be swapped at runtime instead or to treat the
* view model as immutable in which case the setter
* for the Model property should be removed.
*/
public TModel Model
{
get => _model;
set
{
if (ReferenceEquals(_model, value))
{
return;
}
_model = value;
OnPropertyChanged();
}
}
}
Concrete classes
public class GroupViewModel : ViewModelBase<GroupModel>
{
public GroupViewModel(GroupModel model)
: base(model)
{
}
public ObservableCollection<UserViewModel> Users { get; }
= new ObservableCollection<UserViewModel>();
public void AddUser(UserModel user)
{
var viewModel = new UserViewModel(user);
Users.Add(viewModel);
}
}
public class UserViewModel : ViewModelBase<UserModel>
{
public UserViewModel(UserModel model)
: base(model)
{
}
// convenience property; could be done completely in XAML as well
public string FullName => $"{Model.FirstName} {Model.Surname}";
}
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
// create sample user groups
for (var groupIndex = 1u; groupIndex <= 5u; ++groupIndex)
{
var groupName = $"Group {groupIndex}";
var groupModel = new GroupModel(groupIndex, groupName);
var groupViewModel = new GroupViewModel(groupModel);
UserGroups.Add(groupViewModel);
for (var userIndex = 1u; userIndex <= 5u; ++userIndex)
{
var userModel = new UserModel(
id: userIndex,
firstName: "John",
surname: $"Smith",
dateOfBirth: DateTime.Today);
groupViewModel.AddUser(userModel);
}
}
}
public ObservableCollection<GroupViewModel> UserGroups { get; }
= new ObservableCollection<GroupViewModel>();
}
View
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:UserGroups.ViewModels"
x:Class="UserGroups.Views.MainWindow"
Title="User Groups"
Width="1024"
Height="768">
<Window.DataContext>
<viewModels:MainViewModel />
</Window.DataContext>
<Grid>
<TreeView ItemsSource="{Binding Path=UserGroups}">
<TreeView.Resources>
<!-- Template for Groups -->
<HierarchicalDataTemplate DataType="{x:Type viewModels:GroupViewModel}"
ItemsSource="{Binding Path=Users}">
<TextBlock Text="{Binding Path=Model.Name}" />
</HierarchicalDataTemplate>
<!-- Template for Users -->
<DataTemplate DataType="{x:Type viewModels:UserViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Model.Id, StringFormat='[{0}]'}"
Margin="3,0" />
<TextBlock Text="{Binding Path=FullName}"
Margin="3,0" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
Here's what you end up with:
There are lots of frameworks that take care of a lot of the tedious work of working with the MVVM pattern (e.g. removing most/all of the boilerplate code for INotifyPropertyChanged). Here are just a few to look at:
MVVM Light Toolkit
Prism
ReactiveUI
Some additional resources that might also be useful:
SnoopWPF
WPF Tutorial
It's not entirely clear to me what the result is supposed to look like, but here are some initial suggestions.
I wouldn't try nesting an Observable collection inside another one. Instead, define something like a separate Group model class that has a list of User objects as its field.
I take it the user is supposed to enter values for your bound properties in the xaml in order to create a new user? You need to add a button or something that the user can press after filling those values out. The button click should be bound to a RelayCommand (add MVVMLight to the project if necessary) in the view model. The method invoked by said relaycommand would instantiate a new User object using the fields bound in the xaml and add to your ObservableCollection.
<Button Command="{Binding Path=CreateUserCommand}">
<TextBlock Text="Create User"/>
</Button>
Then in the view model...
public RelayCommand CreateUserCommand { get; private set; }
CreateUserCommand = new RelayCommand(() =>
{
User user = new User
{
FirstName = FirstName,
LastName = LastName,
//...etc.
}
collectionOfUsers.Add(user);
});
"I also don't know how to bind the new users to the XAML."
So far I don't see any xaml code that would handle displaying new users. Seems to me you'd want to bind your collection of users to a grid or combo box. After the user enters new user properties in the textboxes and clicks the appropriate button, the grid or combo box would update. You could have separate controls for separate groups. Again, that part is not entirely clear to me.
Given (3), your ObservableCollection of users needs to be a property in the view model that implements OnPropertyChanged.
Hope that helps.

How to bind events and properties of controls in UniformGrid dynamically?

I am a newbie in templating Wpf controls. I use VS2013, WPF 4.5 and Caliburn Micro 2.0.2. In part of tasks I have I need to populate a grid with toggle buttons contained different images and its subtitle. I have solved it using UniformGrid. See my code below. They work but still don't have event and property binding since I don't know how I can bind the events and properties of toggle buttons to view model, since they are generated automatically and dynamically and the number of toggle buttons is uncertain (depends on the number of images in the image folder).
For example:
manually I could bind the Click event, IsChecked property and some other properties of toggle button 1 like following:
<ToggleButton x:Name="ToggleVehicle01" IsChecked={Binding SelectedVehicle01} Background="{Binding BackColorSelectedVehicle01}" ToolTip="{Binding VehicleName01}">
But now I can't do that anymore since the toggle buttons are generated automatically and their number is uncertain. Please help. Feel free to change my code below or give me examples code that works. Thank you in advance.
The View (MainView.xaml):
<UserControl x:Class="CMWpf02.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid Width="1024"
Height="768"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ShowGridLines="True">
<ItemsControl Name="ImageList"
Background="#FFFFFFFF"
BorderBrush="#FFA90606"
ItemsSource="{Binding Path=VehicleImages}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Margin="0,0,0,0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Width="180"
Margin="10,10,10,10"
FontSize="10"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}">
<!-- x:Name="ToggleVehicle01" -->
<!-- Background="{Binding BackColorSelectedVehicle01}" -->
<!-- IsChecked="{Binding SelectedVehicle01}" -->
<!-- ToolTip="{Binding Vehicle01Name}"> -->
<StackPanel Margin="0,5,0,5"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Width="140"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding Path=Image}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding Path=Name}" />
</StackPanel>
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The ViewModel (MainViewModel.cs):
using Caliburn.Micro;
using System;
using System.Collections.ObjectModel;
using System.IO;
namespace CMWpf02.ViewModels
{
public class MainViewModel : Screen, IHaveDisplayName
{
private String _path2Images = #"D:\tmp\Images";
public string DisplayName { get; set; }
public ObservableCollection<VehicleImage> VehicleImages { get; set; }
public MainViewModel()
{
DisplayName = "Main Window";
var vehicles = new ObservableCollection<String>();
vehicles = GetAllFilesFromFolder(_path2Images);
VehicleImages = new ObservableCollection<VehicleImage>();
foreach (var i in vehicles)
VehicleImages.Add(new VehicleImage(i));
}
public ObservableCollection<String> GetAllFilesFromFolder(String fullPathFolder)
{
string[] fileArray = Directory.GetFiles(fullPathFolder);
return new ObservableCollection<String>(fileArray);
}
}
public class VehicleImage
{
public String Image { get; private set; }
public String Name { get; private set; }
public VehicleImage(String image)
{
Image = image;
Name = Path.GetFileName(image);
}
}
//public void ToggleVehicle01()
//{
// var selectText = (SelectedVehicle01) ? " selected" : " unselected";
// MessageBox.Show(Vehicle01Name + selectText);
// BackColorSelectedVehicle01 = (SelectedVehicle01) ? _backColorSelectedVehicle : _defaultBackColorVehicle;
//}
//public Boolean SelectedVehicle02
//{
// get { return _selectedVehicle02; }
// set
// {
// _selectedVehicle02 = value;
// NotifyOfPropertyChange(() => SelectedVehicle02);
// }
//}
//public Brush BackColorSelectedVehicle02
//{
// get { return _backColorSelectedVehicle02; }
// set
// {
// _backColorSelectedVehicle02 = value;
// NotifyOfPropertyChange(() => BackColorSelectedVehicle02);
// }
//public String Vehicle01Name { get; private set; }
}
EDIT: Now I can bind the properties of generated ToggleButton with view model. I make the VehicleImage class to a view model (see modified code below). But I still have problem to bind Click-event of generated ToggleButton to view model.
The modified class to view model
public class VehicleImage : PropertyChangedBase
{
public String Image { get; private set; }
public String Name { get; private set; }
private Boolean _selectedVehicle;
public Boolean SelectedVehicle
{
get { return _selectedVehicle; }
set
{
_selectedVehicle = value;
BackColorSelectedVehicle = _selectedVehicle ? new SolidColorBrush(Color.FromArgb(255, 242, 103, 33)) : new SolidColorBrush(Colors.White);
}
}
private Brush _backColorSelectedVehicle;
public Brush BackColorSelectedVehicle
{
get { return _backColorSelectedVehicle; }
set
{
_backColorSelectedVehicle = value;
NotifyOfPropertyChange(() => BackColorSelectedVehicle);
}
}
// ToggleButton's Click-Event Handler, but it doesn't get event trigger from View.
// Therefore I set the BackColorSelectedVehicle fin setter of SelectedVehicle property.
public void ToggleSelection()
{
//BackColorSelectedVehicle = SelectedVehicle ? new SolidColorBrush(Color.FromArgb(255, 242, 103, 33)) : new SolidColorBrush(Colors.White);
}
public VehicleImage(String image)
{
Image = image;
Name = Path.GetFileName(image);
}
}
The modified view
<ToggleButton Width="180"
Margin="10,10,10,10"
Background="{Binding Path=BackColorSelectedVehicle}"
FontSize="10"
IsChecked="{Binding Path=SelectedVehicle}"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}"
ToolTip="{Binding Path=Name}">
<!-- x:Name="ToggleSelection" -->
<StackPanel Margin="0,5,0,5"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Width="140"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding Path=Image}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Path=Name}" />
</StackPanel>
</ToggleButton>

DataTemplate is not generating ListItemBox

I want to generate ListItemBox using DataTemplate but items are not generating. Please guide me where is the mistake. I have following code in MainWindow.xaml.
<Window x:Class="Offline_Website_Downloader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:bd="clr-namespace:Offline_Website_Downloader"
Title="Offline Website Downloader" Background="#f5f6f7" Height="500" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<bd:BindingController x:Key="BindingControllerKey" />
<DataTemplate x:Key="DownloadedWebsitesListBox">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" Width="Auto">
<TextBlock FontWeight="Bold" FontSize="18" Width="480">
<Hyperlink NavigateUri="http://google.com">
<Label Content="{Binding Path=WebsiteTitle}" />
</Hyperlink>
</TextBlock>
<TextBlock Width="132" TextAlignment="right">
<TextBlock Text="Remaining Time: "/>
<TextBlock Name="TimeRemaining" Text="js"/>
</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ProgressBar Name="progress1" Maximum="100" Minimum="0" Value="30" Background="#FFF" Width="612" Height="10" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" Width="450">Status: <TextBlock Text="{Binding Path=Status}"/></TextBlock>
<TextBlock Width="162" TextAlignment="right">
<TextBlock Text="Downloading Speed: "/>
<TextBlock Name="DownloadingSpeed" Text="{Binding Path=DownloadingSpeed}"/>
</TextBlock>
</StackPanel>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Width="Auto"
Name="WebsiteList"
Grid.Column="1"
Grid.Row="2"
Grid.RowSpan="2"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource DownloadedWebsitesListBox}"
Margin="0,0,0,0">
</ListBox>
</Grid>
</window>
and MainWindow.xaml.cs
BindingController bc = new BindingController();
public MainWindow()
{
InitializeComponent();
bc.DownloadingSpeed = "40kb/s";
bc.WebsiteTitle = "WebsiteTitle";
bc.Status = "Downloading";
DataContext = bc;
}
and BindingController.cs
public class BindingController
{
public BindingController()
{
}
private string _WebsiteTitle;
public string WebsiteTitle
{
set { _WebsiteTitle = value; }
get { return _WebsiteTitle; }
}
private string _Status;
public string Status
{
set { _Status = value ; }
get { return _Status ; }
}
private string _DownloadStartDate;
public string DownloadStartDate
{
set { _DownloadStartDate = value; }
get { return _DownloadStartDate ; }
}
private string _DownloadingSpeed = "0 kb/s";
public string DownloadingSpeed
{
set { _DownloadingSpeed = value; }
get { return _DownloadingSpeed; }
}
}
Your problem is that you're binding a ListBox to an object that contains several properties, but really only represents a single object/state. The ListBox expects to display a list of items (i.e. IList, IBindingList, IEnumerable, ObservableCollection).
Assuming you want to display more than one download at a time, which I'm assuming given that you're using a ListBox, I would refactor the download properties into a separate class. You will also need to implement INotifyPropertyChanged on your properties so that when the values are changed, they will be shown in the UI.
public class Download : INotifyPropertyChanged
{
private string _WebsiteTitle;
public string WebsiteTitle
{
get { return _WebsiteTitle; }
set
{
if (_WebsiteTitle == value)
return;
_WebsiteTitle = value;
this.OnPropertyChanged("WebsiteTitle");
}
}
private string _Status;
public string Status
{
get { return _Status; }
set
{
if (_Status == value)
return;
_Status = value;
this.OnPropertyChanged("Status");
}
}
private string _DownloadStartDate;
public string DownloadStartDate
{
get { return _DownloadStartDate; }
set
{
if (_DownloadStartDate == value)
return;
_DownloadStartDate = value;
this.OnPropertyChanged("DownloadStartDate");
}
}
private string _DownloadingSpeed = "0 kb/s";
public string DownloadingSpeed
{
get { return _DownloadingSpeed; }
set
{
if (_DownloadingSpeed == value)
return;
_DownloadingSpeed = value;
this.OnPropertyChanged("DownloadingSpeed");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The new BindingController:
public class BindingController
{
public BindingController()
{
this.Downloads = new ObservableCollection<Download>();
}
public ObservableCollection<Download> Downloads { get; private set; }
}
Setting up the bindings in XAML:
<ListBox Width="Auto"
Name="WebsiteList"
Grid.Column="1"
Grid.Row="2"
Grid.RowSpan="2"
ItemsSource="{Binding Downloads}"
ItemTemplate="{StaticResource DownloadedWebsitesListBox}"
Margin="0,0,0,0">
</ListBox>
Initializing the collection in MainWindow
Download download = new Download();
download.DownloadingSpeed = "40kb/s";
download.WebsiteTitle = "WebsiteTitle";
download.Status = "Downloading";
bc.Downloads.Add(download);
this.DataContext = bc;

WPF: Binding a List<class> to a ComboBox

I've been working on this problem for about 3 hours now, and I got to a dead end.
Currently I'm trying to bind a list to a ComboBox.
I have used several methods to bind the List:
Code behind:
public partial class MainWindow : Window
{
public coImportReader ir { get; set; }
public MainWindow()
{
ir = new coImportReader();
InitializeComponent();
}
private void PremadeSearchPoints(coSearchPoint sp)
{
//SearchRefPoint.DataContext = ir.SearchPointCollection;
SearchRefPoint.ItemsSource = ir.SearchPointCollection;
SearchRefPoint.DisplayMemberPath = Name;
The data was binded correctly but the DisplayMemeberPath for some reason returned the name of the class and not the name of it's member.
The XAML method returned an empty string...
<ComboBox x:Name="SearchRefPoint" Height="30" Width="324" Margin="0,10,0,0"
VerticalAlignment="Top" ItemsSource="{Binding ir.SearchPointCollection}"
DisplayMemberPath="Name">
I've also tried to fill it with a new list which I create in the MainWindow. the result was the same in both cases.
Also I've tried to create and ListCollectionView which was success, but the problem was that I could get the index of the ComboBox item. I prefer to work by an Id. For that reason I was looking for a new solution which I found at: http://zamjad.wordpress.com/2012/08/15/multi-columns-combo-box/
The problem with this example is that is not clear how the itemsource is being binded.
Edit:
To sum things up: I'm currently trying to bind a list(SearchPointsCollection) of objects(coSearchPoints) defined in a class (coImportReader).
namespace Import_Rates_Manager
{
public partial class MainWindow : Window
{
public coImportReader ir;
public coViewerControles vc;
public coSearchPoint sp;
public MainWindow()
{
InitializeComponent();
ir = new coImportReader();
vc = new coViewerControles();
sp = new coSearchPoint();
SearchRefPoint.DataContext = ir;
}
}
}
//in function....
SearchRefPoint.ItemsSource = ir.SearchPointCollection;
SearchRefPoint.DisplayMemberPath = "Name";
namespace Import_Rates_Manager
{
public class coImportReader
{
public List<coSearchPoint> SearchPointCollection = new List<coSearchPoint>();
}
}
namespace Import_Rates_Manager
{
public class coSearchPoint
{
public coSearchPoint()
{
string Name = "";
Guid Id = Guid.NewGuid();
IRange FoundCell = null;
}
}
}
This results in a filled combobox with no text
Here a simple example using the MVVM Pattern
XAML
<Window x:Class="Binding_a_List_to_a_ComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid HorizontalAlignment="Left"
VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<ComboBox Grid.Column="0" Grid.Row="0" ItemsSource="{Binding SearchPointCollection , UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding MySelectedIndex, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding MySelectedItem, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Id}" Grid.Row="0"/>
<TextBlock Text="{Binding Name}" Grid.Row="1"/>
<TextBlock Text="{Binding Otherstuff}" Grid.Row="2"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Bind NOW" Grid.Column="0" Grid.Row="1" Click="Button_Click"/>
<TextBlock Text="{Binding MySelectedIndex, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="0"/>
<Grid Grid.Column="1" Grid.Row="1"
DataContext="{Binding MySelectedItem, UpdateSourceTrigger=PropertyChanged}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Id}" Grid.Column="0"/>
<TextBlock Text="{Binding Name}" Grid.Column="1"/>
<TextBlock Text="{Binding SomeValue}" Grid.Column="2"/>
</Grid>
</Grid>
Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using Import_Rates_Manager;
namespace Binding_a_List_to_a_ComboBox
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DataContext = new coImportReader();
}
}
}
namespace Import_Rates_Manager
{
public class coImportReader : INotifyPropertyChanged
{
private List<coSearchPoint> myItemsSource;
private int mySelectedIndex;
private coSearchPoint mySelectedItem;
public List<coSearchPoint> SearchPointCollection
{
get { return myItemsSource; }
set
{
myItemsSource = value;
OnPropertyChanged("SearchPointCollection ");
}
}
public int MySelectedIndex
{
get { return mySelectedIndex; }
set
{
mySelectedIndex = value;
OnPropertyChanged("MySelectedIndex");
}
}
public coSearchPoint MySelectedItem
{
get { return mySelectedItem; }
set { mySelectedItem = value;
OnPropertyChanged("MySelectedItem");
}
}
#region cTor
public coImportReader()
{
myItemsSource = new List<coSearchPoint>();
myItemsSource.Add(new coSearchPoint { Name = "Name1" });
myItemsSource.Add(new coSearchPoint { Name = "Name2" });
myItemsSource.Add(new coSearchPoint { Name = "Name3" });
myItemsSource.Add(new coSearchPoint { Name = "Name4" });
myItemsSource.Add(new coSearchPoint { Name = "Name5" });
}
#endregion
#region INotifyPropertyChanged Member
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class coSearchPoint
{
public Guid Id { get; set; }
public String Name { get; set; }
public IRange FoundCell { get; set; }
public coSearchPoint()
{
Name = "";
Id = Guid.NewGuid();
FoundCell = null;
}
}
public interface IRange
{
string SomeValue { get; }
}
}
Here are 3 Classes:
MainWindow which set VM as his Datacontext
coImportReader the Class which presents your properties for your bindings
coSearchPoint which is just a Container for your information
IRange which is just an Interface
The Collection you are binding to needs to be a property of ir not a field.
Also try this :
public coImportReader ir { get; set; }
public <type of SearchPointCollection> irCollection { get { return ir != null ? ir.SearchPointCollection : null; } }
Bind to irCollection and see what errors you get if any.
The DisplayMemberPath should contain the property name of the elements in your collection. Assuming the elements in the SearchPointCollection are of the type SearchPoint and this class has a Property SearchPointName you should set DisplayMemberPath like this:
SearchRefPoint.DisplayMemberPath = "SearchPointName";
Edit:
In your code the class coSearchPoint has the Field Name defined in the Constructor. Name has to be a Property of the class, otherwise the Binding can't work.

Categories