WPF ListView data binding with Image dopesn't work - c#

I have this code, which is supposed to display images of the product in a listview:
namespace BoeshCRM
{
/// <summary>
/// Interaction logic for NewBill.xaml
/// </summary>
public partial class NewBill : Page
{
ObservableCollection<ProdImg> Inventory
{
get { return (ObservableCollection<ProdImg>)GetValue(InventoryProperty); }
set { SetValue(InventoryProperty, value); }
}
public static readonly DependencyProperty InventoryProperty =
DependencyProperty.Register("Inventory", typeof(ObservableCollection<ProdImg>), typeof(MainWindow), null);
public NewBill()
{
InitializeComponent();
Inventory = new ObservableCollection<ProdImg>();
Inventory.Add(new ProdImg(Product.LoadProduct(13L)));
}
}
}
This is the actual XML for the Page
<Page x:Class="BoeshCRM.NewBill"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="NewBill">
<Grid>
<ListView x:Name="lstItems" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" MinWidth="50" MinHeight="50" ItemsSource="{Binding Inventory}">
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding imgFi}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
And this is the code for the helper classes, and the ProdImg struct.
namespace BoeshCRM
{
struct ProdImg
{
public Product p;
public FileInfo imgFi;
public ProdImg(Product _p)
{
p = _p;
imgFi = new FileInfo("C:/" + p.id.ToString() + ".jpg");
}
}
}
However no Image is displayed. Any idea why?
The LoadProduct method is not the problem, it works just fine.

Create property in ProdImg and bind to its FullName
public class ProdImg
{
public Product p;
public FileInfo imgFi;
public ProdImg(Product _p)
{
p = _p;
imgFi = new FileInfo("C:/" + p.id.ToString() + ".jpg");
}
public FileInfo ImgFi
{
get { return imgFi ; }
set {imgFi = value; }
}
}
<Image Source="{Binding ImgFi.FullName}" />

Related

How to get access to a binding to a WPF Binding on C# CodeBehind level

I am a VB.Net programmer and quite new to C#. I am at a point where I'm stuck.
I want to make an app to create a quotation with Word. This Quotation should consist of two Word files.
The Word files are Templates with Bookmarks, so writing to them should be no problem.
I want to have a WPF User Interface where the User can describe the Article and when clicking on a button the two Word files will be created.
I made the WPF User Interface and binded the Textboxes to a cl_Data.cs Class where are Properties such as : Description, FunctionName, etc.
My Problem:
How can i access the Data from the User Interface from my Code Behinde to shift it to the Word files?
The Code:
WPF: How i Bind it on .xaml level
<Window.Resources>
<!-- Binding the Data Class-->
<local:Cl_Data x:Key="Data"
Dealer="Test"
Customer="Tester"
Machine="M***s"
PRJ="123456"
DeliveryTime="6"
Description="Managing different chucks, Saving position data of the linear sensor for chuck clamp unclamp position"
Operation="The operator can select a chuck form the chuck management and save the clamp and unclamp position and reuse this position for next time"
FunctionName="GeneratorAPP"
Requirements="API-Kit"
/>
</Window.Resources>
How i call it on .xaml level (same document) -> This works
<Border BorderBrush="#FFB0F0FF" BorderThickness="1" Height="26">
<TextBox x:Name="Tb_Dealer"
TextWrapping="Wrap" Text="{Binding Dealer, UpdateSourceTrigger=PropertyChanged}" Width="auto" Foreground="#FFB0F0FF" BorderBrush="#00ABADB3" Background="Transparent" TextAlignment="Center" VerticalAlignment="Center" />
</Border>
<Border BorderBrush="#FFB0F0FF" BorderThickness="1" Height="26">
<TextBox x:Name="Tb_Dealer" TextWrapping="Wrap" Text="{Binding Dealer, UpdateSourceTrigger=PropertyChanged}" Width="auto" Foreground="#FFB0F0FF" BorderBrush="#00ABADB3" Background="Transparent" TextAlignment="Center" VerticalAlignment="Center" />
</Border>
So my class cl_Data.cs looks like:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Windows;
namespace QuotationApp.Classes
{
internal class Cl_Data : INotifyPropertyChanged
{
#region Descriptions
private string _Dealer ;
public string Dealer
{
get { return _Dealer; }
set
{ _Dealer = value;
OnPropertyChanged("Dealer");
}
}
private string _Customer;
public string Customer
{
get { return _Customer; }
set
{
_Customer = value;
OnPropertyChanged("Customer");
}
}
private string _Machine;
public string Machine
{
get { return _Machine; }
set
{
_Machine = value;
OnPropertyChanged("Machine");
}
}
private string _PRJ;
public string PRJ
{
get { return _PRJ; }
set
{
_PRJ = value;
OnPropertyChanged(PRJ);
}
}
private string _DeliveryTime;
public string DeliveryTime
{
get { return _DeliveryTime; }
set {
_DeliveryTime = value;
OnPropertyChanged("DeliveryTime");
}
}
private string _Operation;
public string Operation
{
get { return _Operation; }
set {
_Operation = value;
OnPropertyChanged("Operation");
}
}
private string _Description;
public string Description
{
get { return _Description; }
set {
_Description = value;
OnPropertyChanged("Description");
}
}
private string _FunctionName;
public string FunctionName
{
get { return _FunctionName; }
set {
_FunctionName = value;
OnPropertyChanged("FunctionName");
}
}
private string _Requirements;
public string Requirements
{
get { return _Requirements; }
set {
_Requirements = value;
OnPropertyChanged("Requirements");
}
}
#endregion
#region Costs
private double _HardwareCost;
public double HardwareCost
{
get { return _HardwareCost; }
set {
_HardwareCost = value;
_CostTotal = CalcTotal();
OnPropertyChanged("HardwareCost");
}
}
private double _PersonalCost;
public double PersonalCost
{
get { return _PersonalCost; }
set {
_PersonalCost = value;
_CostTotal = CalcTotal();
OnPropertyChanged("PersonalCost");
}
}
private double _TravelCost;
public double TravelCost
{
get { return _TravelCost; }
set {
_TravelCost = value;
_CostTotal = CalcTotal();
OnPropertyChanged("TravelCost");
}
}
private double _CostTotal;
public double CostTotal
{
get { return _CostTotal; }
set {
_CostTotal = value;
OnPropertyChanged("CostTotal");
}
}
public double CalcTotal()
{
double total = 0;
try
{
total = TravelCost + HardwareCost + PersonalCost;
}
catch (Exception e)
{
MessageBox.Show("Error getting the total Value: " + e.Message);
}
return total;
}
#endregion
#region PropertyChangedEvents
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
So now i want to access these Data for example the Description (Data.Description) to process it to a word Bookmark. But how can i Access this Data on WPF level from CodeBehind?
Please be easy with me, i know this question is wierd but i googled 2 days now an i am starting to get frustrated. If this question is answered somewhere else, i would love to have the link to the answer.
Thanks in advance
I made the most simple example as far as it came to my mind.
If you don't understand, ask questions about it.
I will try to answer.
using System;
namespace Core2022.Lexxy_B
{
public class PersonDto
{
public int Id { get; }
public string Name { get; }
public int Age { get; }
public PersonDto(int id, string name, int age)
{
if (Id < 0)
throw new ArgumentOutOfRangeException(nameof(id));
Id = id;
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
Name = name;
if (age < 0)
throw new ArgumentOutOfRangeException(nameof(age));
Age = age;
}
public PersonDto(string name, int age)
: this(0, name, age)
{
Id = -1;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
namespace Core2022.Lexxy_B
{
public class PeopleModel
{
private readonly List<PersonDto> people = new List<PersonDto>()
{
new PersonDto(5, "Thomas", 25),
new PersonDto(1, "Harry", 40),
};
public IReadOnlyList<PersonDto> GetPeople() => Array.AsReadOnly(people.ToArray());
public void AddPerson(PersonDto person)
{
int id = people.LastOrDefault()?.Id ?? 0;
do
{
id++;
} while (people.Any(p => p.Id == id));
person = new PersonDto(id, person.Name, person.Age);
people.Add(person);
AddedPerson?.Invoke(this, person);
}
public event EventHandler<PersonDto>? AddedPerson;
}
}
namespace Core2022.Lexxy_B
{
public class PersonVM
{
public string? Name { get; set; }
public int Age { get; set; }
}
}
using Simplified;
using System.Collections.ObjectModel;
namespace Core2022.Lexxy_B
{
public class PeopleViewModel : ViewModelBase
{
private readonly PeopleModel model = new PeopleModel();
private string _mode = "view";
public ObservableCollection<PersonDto> People { get; } = new ObservableCollection<PersonDto>();
public string ViewMode { get => _mode; private set => Set(ref _mode, value); }
public PeopleViewModel()
{
foreach (var person in model.GetPeople())
{
People.Add(person);
}
model.AddedPerson += OnAddedPerson;
}
private void OnAddedPerson(object? sender, PersonDto newPerson)
{
People.Add(newPerson);
}
public RelayCommand AddPersonCommand => GetCommand<PersonVM>(AddPersonExecute, AddPersonCanExecute);
private void AddPersonExecute(PersonVM person)
{
model.AddPerson(new PersonDto(person.Name ?? string.Empty, person.Age));
ViewMode = "view";
}
private bool AddPersonCanExecute(PersonVM person)
{
return !string.IsNullOrWhiteSpace(person.Name) && person.Age >= 0;
}
public RelayCommand ExitAddingPersonCommand => GetCommand(() => ViewMode = "view");
public RelayCommand BeginAddingPersonCommand => GetCommand(() => ViewMode = "add");
}
}
<Window x:Class="Core2022.Lexxy_B.PeopleWindow"
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:Core2022.Lexxy_B"
mc:Ignorable="d"
Title="PeopleWindow" Height="450" Width="800"
DataContext="{DynamicResource vm}">
<Window.Resources>
<local:PeopleViewModel x:Key="vm"/>
</Window.Resources>
<UniformGrid Columns="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox x:Name="lBox" ItemsSource="{Binding People}" DisplayMemberPath="Name"/>
<Button Grid.Row="1" Content="Go to Add Person" Padding="15 5" Margin="5"
Command="{Binding BeginAddingPersonCommand}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewMode}" Value="add">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
<ContentControl x:Name="cp">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Resources>
<DataTemplate x:Key="view.Template">
<local:PersonDetailsUC/>
</DataTemplate>
<DataTemplate x:Key="add.Template">
<local:AddPersonUC/>
</DataTemplate>
</Style.Resources>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate" Value="{StaticResource add.Template}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewMode}" Value="view">
<Setter Property="Content" Value="{Binding SelectedItem, ElementName=lBox}"/>
<Setter Property="ContentTemplate" Value="{StaticResource view.Template}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</UniformGrid>
</Window>
<UserControl x:Class="Core2022.Lexxy_B.PersonDetailsUC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Core2022.Lexxy_B"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=local:PersonDto}">
<UniformGrid Columns="1">
<TextBlock Text="{Binding Id, StringFormat={}Id: {0}, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Name, StringFormat={}Name: {0}}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Age, StringFormat={}Age: {0}, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</UniformGrid>
</UserControl>
<UserControl x:Class="Core2022.Lexxy_B.AddPersonUC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Core2022.Lexxy_B"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<d:UserControl.DataContext>
<local:PeopleViewModel/>
</d:UserControl.DataContext>
<UserControl.Resources>
<local:PersonVM x:Key="person"/>
</UserControl.Resources>
<UniformGrid Columns="2">
<TextBlock Text="Name" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox Text="{Binding Name, Source={StaticResource person}}" VerticalAlignment="Center" Margin="10"/>
<TextBlock Text="Age" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox Text="{Binding Age, Source={StaticResource person}}" VerticalAlignment="Center" Margin="10"/>
<Button Content="Add" Padding="15 5" VerticalAlignment="Center" HorizontalAlignment="Center"
Command="{Binding AddPersonCommand}"
CommandParameter="{Binding Mode=OneWay, Source={StaticResource person}}"/>
<Button Content="Exit" Padding="15 5" VerticalAlignment="Center" HorizontalAlignment="Center"
Command="{Binding ExitAddingPersonCommand}"/>
</UniformGrid>
</UserControl>
Addition due to the Repository.
change Line 33 of Word.cs as i was unable to assign a relative path to open to word template
An example of the implementation of obtaining the full name of files by their path given by a relatively executable assembly.
The file itself in the Project resources must have the properties "Content" - "Copy ...". I have a Russified Studio, so the screenshot is in Russian.
The "bin" folder must not be included in the Project, otherwise, all its contents will also be included in the Assembly.
And here is the code for converting a relative path to an absolute one:
public static readonly string ApplicationFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
public const string TestRelativeNameFile = #"Resources\Word\Test.docx";
public static readonly string TestFullNameFile = Path.Combine(ApplicationFolder, TestRelativeNameFile);
public static void GenerateLocalSolution()
{
try
{
WordApp = new Microsoft.Office.Interop.Word.Application();
TestApp = WordApp.Documents.Open(TestFullNameFile);
}
i bind the DataContext in MainWindow.cs (CodeBehind) to a Class and create a specific Instance of the Object which i access from every other class
This implementation doesn't play well with WPF and OOP.
For example, in Designer mode (when you edit XAML in Studio), you have an empty value in the DataContext. And because of this, you can't use the Binding Builder in development.
If you have an instance of the Cl_Data class specific to each MainWindow instance, then it should be initialized in the Window's XAML:
<Window.Resources>
<classes:Cl_Data
xmlns:classes="clr-namespace:QuotationApp.Classes"
x:Key="data"/>
</Window.Resources>
If the Cl_Data instance is the only one in the application sense and the entire session exists, then you can create it in the App resources:
<Application.Resources>
<classes:Cl_Data
xmlns:classes="clr-namespace:QuotationApp.Classes"
x:Key="data"/>
</Application.Resources>
The window gets it in the DataContext in XAML too.
If its value is needed in the Code Behind, then it must be retrieved either by key or from the DataContext.
<Window x:Class="QuoteApp_EldHasp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
------------------
Title="MainWindow" Height="900" Width="1800"
DataContext="{DynamicResource data}">
// Creating such a public property is a bad idea. But for now, I'm not getting involved.
public /* static */ Cl_Data Data { get; } //= new Cl_Data();
public MainWindow()
{
InitializeComponent();
Data = (Cl_Data)DataContext;
// Or
Data = (Cl_Data)FindResource("data");
}
Next note.
In the Word.GenerateLocalSolution() method, the line "descRange.Text = MainWindow.Data.Dealer" is a VERY STRONG OOP violation. The Word class contains logic for working with data (Domain Logic) - this is its "single-responsibility". And it should not work with UI elements. And "MainWindow.Data" is the View property!
There are several options for correct implementation.
One of them is to get the desired value in the parameter and let the one who called this method decide where to get it from.
public static void GenerateLocalSolution(string text)
{
// Some Code
descRange.Text = text; // MainWindow.Data.Dealer;
private void GenerateButton_Click(object sender, RoutedEventArgs e)
{
var data = (Cl_Data)DataContext;
Word.GenerateLocalSolution(data.Dealer);
}
You should also replace clickers with commands. This will significantly improve the architecture of the application and make the code easier.

WPF Usercontrol Bindings with MVVM ViewModel not working

I've spent some time trying to solve this problem but couldn't find a solution.
I am trying to bind commands and data inside an user control to my view model. The user control is located inside a window for navigation purposes.
For simplicity I don't want to work with Code-Behind (unless it is unavoidable) and pass all events of the buttons via the ViewModel directly to the controller. Therefore code-behind is unchanged everywhere.
The problem is that any binding I do in the UserControl is ignored.
So the corresponding controller method is never called for the command binding and the data is not displayed in the view for the data binding. And this although the DataContext is set in the controllers.
Interestingly, if I make the view a Window instead of a UserControl and call it initially, everything works.
Does anyone have an idea what the problem could be?
Window.xaml (shortened)
<Window x:Class="Client.Views.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:Client.Views"
mc:Ignorable="d">
<Window.Resources>
<local:SubmoduleSelector x:Key="TemplateSelector" />
</Window.Resources>
<Grid>
<StackPanel>
<Button Command="{Binding OpenUserControlCommand}"/>
</StackPanel>
<ContentControl Content="{Binding ActiveViewModel}" ContentTemplateSelector="{StaticResource TemplateSelector}">
<ContentControl.Resources>
<DataTemplate x:Key="userControlTemplate">
<local:UserControl />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
MainWindowViewModel (shortened)
namespace Client.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private ViewModelBase mActiveViewModel;
public ICommand OpenUserControlCommand { get; set; }
public ViewModelBase ActiveViewModel
{
get { return mActiveViewModel; }
set
{
if (mActiveViewModel == value)
return;
mActiveViewModel = value;
OnPropertyChanged("ActiveViewModel");
}
}
}
}
MainWindowController (shortened)
namespace Client.Controllers
{
public class MainWindowController
{
private readonly MainWindow mView;
private readonly MainWindowViewModel mViewModel;
public MainWindowController(MainWindowViewModel mViewModel, MainWindow mView)
{
this.mViewModel = mViewModel;
this.mView = mView;
this.mView.DataContext = mViewModel;
this.mViewModel.OpenUserControlCommand = new RelayCommand(ExecuteOpenUserControlCommand);
}
private void OpenUserControlCommand(object obj)
{
var userControlController = Container.Resolve<UserControlController>(); // Get Controller instance with dependency injection
mViewModel.ActiveViewModel = userControlController.Initialize();
}
}
}
UserControlSub.xaml (shortened)
<UserControl x:Class="Client.Views.UserControlSub"
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:Client.Views"
xmlns:viewModels="clr-namespace:Client.ViewModels"
mc:Ignorable="d">
<Grid>
<ListBox ItemsSource="{Binding Models}" SelectedItem="{Binding SelectedModel}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Attr}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel>
<Button Command="{Binding Add}">Kategorie hinzufügen</Button>
</StackPanel>
</Grid>
</UserControl>
UserControlViewModel (shortened)
namespace Client.ViewModels
{
public class UserControlViewModel : ViewModelBase
{
private Data _selectedModel;
public ObservableCollection<Data> Models { get; set; } = new ObservableCollection<Data>();
public Data SelectedModel
{
get => _selectedModel;
set
{
if (value == _selectedModel) return;
_selectedModel= value;
OnPropertyChanged("SelectedModel");
}
}
public ICommand Add { get; set; }
}
}
UserControlController (shortened)
namespace Client.Controllers
{
public class UserControlController
{
private readonly UserControlSub mView;
private readonly UserControlViewModel mViewModel;
public UserControlController(UserControlViewModel mViewModel, UserControlSub mView)
{
this.mViewModel = mViewModel;
this.mView = mView;
this.mView.DataContext = mViewModel;
this.mViewModel.Add = new RelayCommand(ExecuteAddCommand);
}
private void ExecuteAddCommand(object obj)
{
Console.WriteLine("This code gets never called!");
}
public override ViewModelBase Initialize()
{
foreach (var mod in server.GetAll())
{
mViewModel.Models.Add(mod);
}
return mViewModel;
}
}
}

How to show all the data in one ListBox WPF

I am new with WPF and I am struggling a little bit, trying to display all data in a list I can see the objects there in the view(two lines) but nothing is displayed like Name ?? any recommendation ?? Thank you
xaml :
<ListBox x:Name="nadjib">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
cs :
public ExecuteSpecificJobView()
{
InitializeComponent();
List<string> ext = new List<string>();
ext.Add(".txt");
//itemsControl. = new List<string>(){ "ok","yes" };
ObservableCollection<ViewModels.CreateJobViewModel.Model.TaskJsonAttribute> jobs = new ObservableCollection<ViewModels.CreateJobViewModel.Model.TaskJsonAttribute>();
jobs.Add(new ViewModels.CreateJobViewModel.Model.TaskJsonAttribute { name = "aliii", source = "ok", target = "yess", type = "complete", extension = ext });
jobs.Add(new ViewModels.CreateJobViewModel.Model.TaskJsonAttribute { name = "aliii", source = "ok", target = "yess", type = "complete", extension = ext });
nadjib.ItemsSource = jobs;
}
public class TaskJsonAttribute
{
public string name;
public string source;
public string target;
public string type;
public List<string> extension;
public string Name
{
get { return name; }
}
}
Here is a little project that is similar to yours. This is showing the two names in the Listbox. See how this differ from yours.
XAML
<Window x:Class="WpfApp9.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:WpfApp9"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListBox x:Name="nadjib">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApp9
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<TaskJsonAttribute> jobs = new ObservableCollection<TaskJsonAttribute>();
jobs.Add(new TaskJsonAttribute() { name = "aliii-1" });
jobs.Add(new TaskJsonAttribute() { name = "aliii-2" });
nadjib.ItemsSource = jobs;
}
}
public class TaskJsonAttribute
{
public string name;
public string source;
public string target;
public string type;
public List<string> extension;
public string Name
{
get { return name; }
}
}
}

WPF Sorting an ObservableCollection de-selects a ComboBox

I have a ComboBox where a user can select what JobType they are working on. The ComboBox has a list of AllJobTypes. The problem stems from when a user adds a new JobType, I add the JobType to the AllJobTypes ObservableCollection, then sort it. When the sorting happens the ComboBox get's de-selected and not really sure why. The JobConfig.SelectedJobType.Name never changes in this process. Is there a way to sort an observable collection where it doesn't break the ComboBox?
public class JobTypeList : ObservableCollection<JobType> {
public void SortJobTypes() {
var sortableList = new List<JobType>(this);
sortableList.Sort((x, y) => x.Name.CompareTo(y.Name));
//this works but it creates a bug in the select for JobTypes
for (int i = 0; i < sortableList.Count; i++) {
this.Move(this.IndexOf(sortableList[i]), i);
}
}
And in the XAML
<ComboBox Grid.Column="0" SelectionChanged="JobTypeComboBox_SelectionChanged"
Name="JobTypeComboBox"
ItemsSource="{Binding Path=AllJobTypes}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding
Path=JobConfig.SelectedJobType.Name}" />
Instead of sorting the collection in the view model, you should bind the ComboBox's ItemsSource to a CollectionViewSource, where you can specify a SortDescription:
<Window ...
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
...>
<Window.Resources>
<CollectionViewSource x:Key="cvs" Source="{Binding AllJobTypes}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Name"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
...
<ComboBox ItemsSource="{Binding Source={StaticResource cvs}}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding JobConfig.SelectedJobType.Name}"/>
...
</Window>
For further information see How to: Sort and Group Data Using a View in XAML
Here's a version using ItemsSource/SelectedItem. Note that you can add a new item to the list and sort it without losing the currently selected item in the view.
The window
<Window
x:Class="SortingAList.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:SortingAList"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox
Text="{Binding NewJobType, Delay=1000}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="200" />
<ComboBox
Grid.Row="1"
ItemsSource="{Binding JobTypes}"
SelectedItem="{Binding SelectedJobType}"
DisplayMemberPath="Name"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="200" />
</Grid>
</Window>
The code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify([CallerMemberName]string property = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public class ViewModel : Notifier
{
private JobType _selectedJobType;
private string _newJobType;
public JobTypeList JobTypes { get; private set; }
public JobType SelectedJobType { get => _selectedJobType; set { _selectedJobType = value; Notify(); } }
public string NewJobType { get => _newJobType; set { _newJobType = value; Notify(); AddNewJobType(value); } }
public ViewModel()
{
JobTypes = new JobTypeList();
JobTypes.Add(new JobType { Name = "Butcher" });
JobTypes.Add(new JobType { Name = "Baker" });
JobTypes.Add(new JobType { Name = "LED maker" });
}
private void AddNewJobType(string name)
{
if(JobTypes.Any(x => x.Name == name)) return;
JobTypes.Add(new JobType { Name = name });
JobTypes.SortJobTypes();
}
}
public class JobType : Notifier
{
private string _name;
public string Name { get => _name; set { _name = value; Notify(); } }
}
Using your JobTypesList
public class JobTypeList : ObservableCollection<JobType>
{
public void SortJobTypes()
{
var sortableList = new List<JobType>(this);
sortableList.Sort((x, y) => x.Name.CompareTo(y.Name));
//this works but it creates a bug in the select for JobTypes
for(int i = 0; i < sortableList.Count; i++)
{
this.Move(this.IndexOf(sortableList[i]), i);
}
}
}

C# WPF DataGrid crashes app when changing cell value thats binded to an ObservableCollection

I'm new to this databinding stuff. but I don't know what I'm doing wrong.
I have a simple form with a datagrid on it. I create an observablecollection of a class. the class is just two properties. I bind to the observablecollection and the datagrid shows has the data shown. so that's working but if I go to change change a value in the datagrid by double clicking on the cell. when I hit enter it crashes.
here is code
namespace TESTDELETE
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Initialized(object sender, EventArgs e)
{
ObservableCollection<Basemap> BMList;
{
BMList = new ObservableCollection<Basemap>();
BMList.Add(new Basemap("filename1", "desc1"));
BMList.Add(new Basemap("filename2", "desc2"));
BMList.Add(new Basemap("filename3", "desc3"));
}
datagrid1.DataContext = BMList;
}
}
}
here is my class
namespace TESTDELETE
public class Basemap
{
private string bmfilename;
private string bmdesc;
public Basemap(string filename, string desc)
{
this.bmfilename = filename;
this.bmdesc = desc;
}
public string BMFileName {
get {
return bmfilename;
}
set {
BMFileName = value;
}
}
public string BMDesc
{
get
{
return bmdesc;
}
set
{
BMDesc = value;
}
}
}
}
and here is my XAML
<Window x:Class="TESTDELETE.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" Initialized="Window_Initialized">
<Border BorderBrush="Black" BorderThickness="1">
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="339,175,0,0" VerticalAlignment="Top" Width="75"/>
<TabControl Margin="10">
<TabItem Header="TabItem">
<Grid Background="#FFE5E5E5">
<DataGrid x:Name="datagrid1" Margin="10" ItemsSource="{Binding}" AutoGenerateColumns="True">
</DataGrid>
</Grid>
</TabItem>
<TabItem Header="TabItem">
<Grid Background="#FFE5E5E5"/>
</TabItem>
</TabControl>
</Grid>
</Border>
the error happens in basemap class. when trying to change the value of a cell in the datagrid the error happens in the set function of the property that's changed.
for example after the form loads I change the cell that contains the value desc3. the error would happen in the set function of the BMDesc in the Basemap class.
the error says an unhandled exception of type 'System.StackOverflowException' occurred in the TESTDELETE.exe
Any help would be awesome.
JD
The BMFileName and BMDesc property setters should not set their own value because it will generate an infinite loop which crashes your application. You should valorize private members instead:
public class Basemap
{
private string bmfilename;
private string bmdesc;
public Basemap(string filename, string desc)
{
this.bmfilename = filename;
this.bmdesc = desc;
}
public string BMFileName
{
get
{
return bmfilename;
}
set
{
bmfilename = value;
}
}
public string BMDesc
{
get
{
return bmdesc;
}
set
{
bmdesc = value;
}
}
}

Categories