I'm trying to create a Combobox with multiple items and header and way I'm approaching is to create a listView (or DataGrid) inside the Combobox
but for some reason, the items won't bind
as you can see no items in the list
XAML (when ComboBox.ItemTemplate have used the items showed properly with no headers of course)
<ComboBox
materialDesign:HintAssist.Hint="בחר מתכון מהרשימה">
<!--ItemsSource = "{Binding Path=Recipes}" >-->
<!--DisplayMemberPath = "Description">-->
<ListView ItemsSource="{Binding Recipes}"
SelectedItem="{Binding Path=SelectedRecipe}"
Height="200" ScrollViewer.VerticalScrollBarVisibility="Visible" IsEnabled="False" Focusable="False">
<ListView.View>
<GridView>
<GridViewColumn Width="130" Header="Description" DisplayMemberBinding="{Binding Description}" />
</GridView>
</ListView.View>
</ListView>
</ComboBox>
ViewModel (im using Prism as my MVVM library)
public ObservableCollection<Recipes> Recipes
{
get { return _recipes; }
set { SetProperty(ref _recipes, value); }
}
private ObservableCollection<Recipes> _recipes = new ObservableCollection<Recipes>();
private async void FillRecipesList() //this is call on program startup
{
if (Recipes != null && Recipes.Count > 0)
{
Recipes.Clear();
}
var result = await _mSql.GetRecipes();
if (result.Count() > 0)
Recipes.AddRange(result);
}
Model
public class Recipes
{
public long AutoNum { get; set; }
public int? RecipeCode { get; set; }
public int? RecipeVersion { get; set; }
public string Description { get; set; }
}
You should try to call the method
FillRecipesList()
in your ViewModel's constructor
Related
This question already has answers here:
Why does WPF support binding to properties of an object, but not fields?
(2 answers)
Closed 8 months ago.
I Have a data grid of objects (CopyObject) , each object contains list of objects (PGroupGridObject) that i want to display inside combo box.
My View Model :
private ObservableCollection<CopyObject> foldersToCopy;
public ObservableCollection<CopyObject> FoldersToCopy
{ get => foldersToCopy; set { foldersToCopy = value; OnPropertyChanged("FoldersToCopy"); } }
CopyObject class :
public class CopyObject
{
public ObservableCollection<PGroupGridObject> pGroups;
public CopyObject(string s , ObservableCollection<PGroupGridObject> pGroups)
{
Name = s;
IsFolder = (File.GetAttributes(Name) & FileAttributes.Directory) == FileAttributes.Directory;
this.pGroups = pGroups;
}
public string Name { get; set; }
}
PGroupGridObject class :
public class PGroupGridObject : ViewModelBase
{
public PGroupGridObject(object o)
{
Object = (DeployTool.Domain.Model.PGroup)o;
PGroupName = Object.Name;
ProductName = Object.Project_Product.Name;
}
private bool isChecked { get; set; }
private string pGroupName;
public string PGroupName
{
get { return pGroupName; }
set
{
pGroupName = value;
OnPropertyChanged("PGroupName");
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
OnPropertyChanged("IsChecked");
}
}
}
my xaml :
<DataGrid ItemsSource="{Binding FoldersToCopy}" Grid.Row="7" Grid.Column="1" SelectionUnit="FullRow" RowHeaderWidth="0" SelectedItem="{Binding CopyObjectSelected}"
Style="{StaticResource DataGridStyle}" d:ItemsSource="{d:SampleData ItemCount=2}" AutoGenerateColumns="False"
MinHeight="60" MaxHeight="180" Margin="0,10,0,10"
Foreground="White" Background="#292929" FontSize="16" BorderThickness="0" HorizontalAlignment="Right" MaxWidth="800" MinWidth="600" >
<DataGrid.Columns>
<DataGridTextColumn Header="File Path" Binding="{Binding Name}" CanUserResize="True" MinWidth="520"/>
<DataGridComboBoxColumn Header="PGroups"
ItemsSource="{Binding pGroups, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="{Binding PGroupName}"
/>
</DataGrid.Columns>
</DataGrid>
All the examples I found show Bind from the View Model and not from the object in the grid.
the goal eventually is to display check box inside every group in combo box.
I would appreciate help.
pGroups must be a public property for you to be able to bind to it:
public ObservableCollection<PGroupGridObject> pGroups { get; }
Also, the DisplayMemberPath should be set to a string that matches the name of a property of a PGroupGridObject:
DisplayMemberPath="PGroupName"
I have a ListView which have a column containing a radiobuttons and a column containing checkboxes. What I would like to do is bind this radiobutton to a value based on a int value on a property.
My ListView look like this:
<ListView x:Name="lvSalesmen" ItemsSource="{Binding}" Margin="311,32,0,244" Grid.ColumnSpan="5">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" DisplayMemberBinding="{Binding Path=Id}"/>
<GridViewColumn Header="First Name" DisplayMemberBinding="{Binding Path=FirstName}"/>
<GridViewColumn Header="Last Name" DisplayMemberBinding="{Binding Path=LastName}"/>
<GridViewColumn Header="IsResponsible">
<GridViewColumn.CellTemplate>
<DataTemplate>
<RadioButton GroupName="IsResponsible" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="IsSecondary">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
and this is where I set my DataContext in the code-behind file:
private void Vm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
lvSalesmen.DataContext = vm.Salesmen;
}
My ViewModel look like this:
public class DistrictsListViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private List<Salesman> _salesman;
private ISalesmenService _salesmenService = new SalesmenService();
public DistrictsListViewModel()
{
}
public async void GetAllSalesmenWithResponsibilityByDistrictId( int id)
{
Salesmen = await _salesmenService.GetSalesmanReponsibilityByDistrictIdAsync(id);
}
public List<Salesman> Salesmen {
get { return _salesman; }
set {
_salesman = value;
PropertyChanged(this, new PropertyChangedEventArgs("Salesmen"));
}
}
Salesmen is a List where the Salesman object looks like this:
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int IsResponsible { get; set; }
public int IsSecondary { get; set; }
What I would like to achieve is to check the radiobutton in the row where the 'IsResponsible'-property value equals 1. Only one radiobutton can be checked at a time. This I have achieved by adding the 'GroupName'.
Furthermore, the checkbox should be checked in the 'IsSecondary'-column, if the IsSecondary-property value equals 1. Multiple checkboxes can be checked at the same time.
You have a general type mismatch problem. IsChecked is generally a bool value but there are only int values available.
There are two ways to work this out:
You could change the type of IsResponsible and IsSecondary to bool in GetSalesmanReponsibilityByDistrictIdAsync
You can add another property to bind to which internally converty the value of IsResponsible and IsSecondary to bool in the Getoperation
With either one of these suggestions you can then bind the given value to IsChecked in you WPF like:
<RadioButton IsChecked="{Binding IsResponsibleBool, Mode=OneWay}" GroupName="IsResponsible" />
<CheckBox IsChecked="{Binding IsResponsibleBool, Mode=OneWay}"/>
Possbile solution for suggestion #2:
public Boolean IsResponsibleBool
{
get => this.IsResponsbile == 1;
}
public Boolean IsSecondaryBool
{
get => this.IsSecondary == 1;
}
I see similar questions regarding ListView not updating in MVVM, however I have been struggling for quite a while already..
I have 2 classes, 1 of which is part of the other, such as:
public class Info
{
public string Name { get; set; }
public string Status { get; set; }
....
public ObservableCollection<Content> UserContent { get; set; } = new ObservableCollection<Content>();
}
public class Content
{
public string Filename { get; set; }
public string Path { get; set; }
public string Type { get; set; }
}
The page is divided into 2 columns, left is itemscontrol and right is a single usercontrol. When an itemscontrol is clicked, it passes the datacontext to the usercontrol
<local:ScreenControl DataContext="{Binding MainPageViewModel.SelectedDevice,
Source={x:Static local:ViewModelLocator.Instance}}" />
UserControl contains a TabControl. One of the tab display details from the Info class
.....
<TextBlock Style="{StaticResource localTextBlock}" Text="{Binding Name}" />
<TextBlock Style="{StaticResource localTextBlock}" Text="{Binding Location}" />
.....
And up to here, all is good.
Problem starts on the other tab. I have a button that will OpenFileDialog, browse to video file and add the file to the UserContent. Then the listview should display.
public ICommand AddVidCommand { get; set; }
AddVidCommand = new RelayCommand(AddVid);
public void AddVid()
{
if (MainPageViewModel.SelectedDevice is ScreenInfo info)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Video Files|*.mp4;*.mkv;*.wmv";
if (openFileDialog.ShowDialog() == true)
{
info.UserContent.Add(new Content
{
Filename = openFileDialog.SafeFileName,
Path = openFileDialog.FileName
});
}
}
}
<TabItem Header="Playlist">
<StackPanel Orientation="Vertical" >
<Button Content="Add"
Command="{Binding ScreenControlViewModel.AddVidCommand,
Source={x:Static local:ViewModelLocator.Instance}}"
CommandParameter="{Binding}"/>
<ListView x:Name="fileList"
ItemsSource="{Binding UserContent}">
<ListView.View>
<GridView>
<GridViewColumn Width="100" DisplayMemberBinding="{Binding Type}" Header="Type" />
<GridViewColumn Width="100" DisplayMemberBinding="{Binding Filename}" Header="Name" />
<GridViewColumn Width="100" DisplayMemberBinding="{Binding Path}" Header="Path" />
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</TabItem>
Adding MainPageViewModel
public class MainPageViewModel : BaseViewModel
{
public static ObservableCollection<ScreenInfo> Devices { get; set; }
public static ScreenInfo SelectedDevice { get; set; }
public MainPageViewModel()
{
Devices = new ObservableCollection<ScreenInfo>();
}
}
As you probably guessed it, my listview does not update. I can clearly see the info being passed to the class in visual studio via the button command, but the UI doesn't show..
Answer provided by Clemens in a comment on the question:
You are mixing static and non-static members in your classes. Each time an instance of MainPageViewModel is created, the static Devices property value is replaced with a new ObservableCollection, without notifying consumers of this property. Don't use static properties with MVVM.
I have created ListView (C#-WPF) with three columns: Number, Action and File. First of all I'm adding several different Items to the ListView, the 'Number' is increasing with each entry added, the 'Action' is recording what exactly happened (Moved/Renamed/Removed) and the 'File' column displays for which file an action occurred.
XAML:
<ListView x:Name="ActionFile" HorizontalAlignment="center" Height="100" VerticalAlignment="bottom" Width="780" Margin="20,0,0,0">
<ListView.View>
<GridView>
<GridViewColumn Header="Number" Width="40" DisplayMemberBinding="{Binding NumberX}"/>
<GridViewColumn Header="Action" Width="200" DisplayMemberBinding="{Binding ActionX}"/>
<GridViewColumn Header="File" Width="350" DisplayMemberBinding="{Binding FileX}"/>
</GridView>
</ListView.View>
</ListView>
C#:
public class FileActionEntry
{
public int NumberX { get; set; }
public string ActionX { get; set; }
public string FileX { get; set; }
}
ActionFile.Items.Add(new FileActionEntry() { NumberX = numValue, ActionX = actionValue, FileX = fileValue });
Now, I was trying to create a foreach loop that would check whether a specific action was taken for a specific file, then to clear that entry from a ListView and return its 'Number' value. I had several different approaches but I couldn't find out how to extract a column values out of an item. I thought it could be done by using '.SubItems' but it seems that it does not work for a WPF.
The best way of doing so is to consider binding your listView ItemSource to an ObservableCollection, implement the INotifyPropertyChanged Interface, and any update to that collection will be automatically reflected on the UI.
Consider the following example based on yours, where the entry which the fileName is provided in the TextBox is removed from the collection:
Xaml
<Grid>
<Grid VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Content="Process" Click="ButtonBase_OnClick"/>
<TextBox Grid.Column="1" Margin="2" Text="{Binding SearchText, Mode=TwoWay}"/>
</Grid>
<ListView x:Name="ActionFile" HorizontalAlignment="center" Height="100" VerticalAlignment="bottom" Width="780" Margin="20,0,0,0" ItemsSource="{Binding FileActionEntryCollection}">
<ListView.View>
<GridView>
<GridViewColumn Header="Number" Width="40" DisplayMemberBinding="{Binding NumberX}"/>
<GridViewColumn Header="Action" Width="200" DisplayMemberBinding="{Binding ActionX}"/>
<GridViewColumn Header="File" Width="350" DisplayMemberBinding="{Binding FileX}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
Code behind
public class FileActionEntry
{
public int NumberX { get; set; }
public string ActionX { get; set; }
public string FileX { get; set; }
}
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string _searchText = "";
public string SearchText
{
get
{
return _searchText;
}
set
{
if (_searchText == value)
{
return;
}
_searchText = value;
OnPropertyChanged();
}
}
private ObservableCollection<FileActionEntry> _fileActionEntryCollection = new ObservableCollection<FileActionEntry>()
{
new FileActionEntry(){ ActionX = "Moved", FileX = "File1", NumberX = 1},
new FileActionEntry(){ ActionX = "Renamed", FileX = "File2", NumberX = 2},
new FileActionEntry(){ ActionX = "Removed", FileX = "File3", NumberX = 3}
};
public ObservableCollection<FileActionEntry> FileActionEntryCollection
{
get
{
return _fileActionEntryCollection;
}
set
{
if (_fileActionEntryCollection == value)
{
return;
}
_fileActionEntryCollection = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (FileActionEntryCollection.Any(f => f.FileX == SearchText))
FileActionEntryCollection.Remove(FileActionEntryCollection.First(f => f.FileX == SearchText));
}
}
You should (must) use data binding (Data Binding Overview in WPF). This will make your life a lot easier. An essential class in the context of data binding is the ObservableColllection. This collection will notify the binding target about any changes (e.g., add or remove). All ItemsControl like the ListView listen to this changes and will update their view to reflect those changes. So you never need to access the control directly to add or remove items.
The view model is the binding source:
class ViewModel : INotifyPropertyChanged
{
// Ctor
public ViewModel() => this.FileActionEntries = new ObservableCollection<FileActionEntry>();
private void AddFileActionToListView()
{
var newFileEntry = new FileActionEntry() { NumberX = numValue, ActionX = actionValue, FileX = fileValue };
// Add an item to the ListView
// or any other control that binds to FileActionEntries (ObservableCollection)
this.FileActionEntries.Add(newFileEntry);
}
private int CheckFileAction(string action, string file)
{
// Throws an exception if file not found
FileActionEntry fileEntry = this.FileActionEntries.First(entry => entry.FileX.Equals(file, StringComparison.OrdinalIgnoreCase));
// TODO: Check action
// Remove an item from the ListView
// or any other control that binds to FileActionEntries (ObservableCollection)
this.FileActionEntries.Remove(fileEntry);
return fileEntry.NumberX;
}
private ObservableCollection<FileActionEntry> fileActionEntries;
public ObservableCollection<FileActionEntry> FileActionEntries
{
get => this.fileActionEntries;
set
{
this.fileActionEntries = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML with a bound ItemsSource:
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<ListView x:Name="ActionFile"
ItemsSource="{Binding FileActionEntries}" >
<ListView.View>
<GridView>
<GridViewColumn Header="Number" Width="40" DisplayMemberBinding="{Binding NumberX}"/>
<GridViewColumn Header="Action" Width="200" DisplayMemberBinding="{Binding ActionX}"/>
<GridViewColumn Header="File" Width="350" DisplayMemberBinding="{Binding FileX}"/>
</GridView>
</ListView.View>
</ListView>
</Window>
I have datagrid, which should allow user add row. But it should add item where one of the field defined by the one of the model properties. Have anybody an idea how to do it.
Code like this:
public class OrderWindowModel : BaseModel
{
public ObservableCollection<GoodM> Goods { get; set; }
public Service Service { get; set; }
}
public class GoodM : BaseModel
{
public Service Service { get; set; }
public List<Good> Goods
{
get{ return Service.GoodsL; }
}
public Good CurrGood { get; set; }
}
And xaml
<custom:DataGrid Margin="5" Grid.Row ="1" CanUserAddRows="True" CanUserDeleteRows="True"
AutoGenerateColumns="False" SnapsToDevicePixels="True" SelectedIndex="0"
CanUserReorderColumns="True" ItemsSource="{Binding Goods}" Grid.ColumnSpan="2">
<custom:DataGrid.Columns>
<custom:DataGridComboBoxColumn Header="Товар" DisplayMemberPath="{Binding Name}"
SelectedItemBinding="{Binding CurrGood}" ItemsSource="{Binding Goods}" Width="*">
</custom:DataGridComboBoxColumn>
</custom:DataGrid.Columns>
</custom:DataGrid>
You can do that using code behind. Hook to InitializingNewItem event in your XAML:
<DataGrid InitializingNewItem="DataGrid_InitializingNewItem"/>
In the handler you will get NewItem added where you can set the value for your field:
private void DataGrid_InitializingNewItem(object sender,
InitializingNewItemEventArgs e)
{
if (e.NewItem != null)
{
((GoodM)newItem).Service = // Set value here;
}
}