I'm just starting out with WPF and MVVM framework. I have a Window with telerik RadGridView and I would like to add data from multipul rows in the same time. Has anyone got any advice or examples ,I've tried numerous ways but none seem to work out.
Thank you
My ViewModel
private IList<Ligne> _CurrentLigne;
public IList<Ligne> CurrentLigne
{
get { return _CurrentLigne; }
set
{
_CurrentLigne= value;
OnPropertyChanged("CurrentLigne");
}
}
var _ligne = Currentligne as Ligne;
foreach (Ligne ligne in CurrentLigne)
{
if (Currentligne!= null)
_ligneBLL.InsetLigne(ligne);
}
My View
<telerik:RadGridView x:Name="GridView"
AutoGenerateColumns="False"
ItemsSource="{Binding ListeLigne}"
SelectedItem="{Binding CurrentLigne, Mode=TwoWay}"
SelectionMode="Multiple" >
Try This !!
foreach (Ligne ligne in ListLigne)
{
var _ligne = ligne as Ligne;
_ligneBLL.InsetLigne(ligne);
}
I recommend that you read the Data Binding Overview page on MSDN so that you can get a better idea on data binding. For now, I can give you a few tips. Firstly, in WPF, your property should really have used an ObservableCollection<T>, like this:
private ObservableCollection<Ligne> _ListeLigne = new ObservableCollection<Ligne>();
public ObservableCollection<Ligne> ListeLigne
{
get { return _ListeLigne; }
set
{
_ListeLigne = value;
OnPropertyChanged("ListeLigne");
}
}
Then your selected item like this:
private Ligne _CurrentLigne = new Ligne();
public Ligne CurrentLigne
{
get { return _CurrentLigne; }
set
{
_CurrentLigne= value;
OnPropertyChanged("CurrentLigne");
}
}
With properties like this, your XAML would be fine. Lastly, to add your items, you simply do this:
ListeLigne = new ObservableCollection<Ligne>(SomeMethodGettingYourData());
Or just...:
ListeLigne = SomeMethodGettingYourData();
... if your data access method returns an ObservableCollection<Ligne>. If you want to select a particular element in the UI, then you must select an actual item from the data bound collection, but you can do that easily using LinQ.
using System.Linq;
CurrentLigne = ListeLigne.First(l => l.SomeLigneProperty == someValue);
Or just:
CurrentLigne = ListeLigne.ElementAt(someValidIndexInCollection);
Oh... and I've got one other tip for you. In your code:
foreach (Ligne ligne in CurrentLigne)
{
if (Currentligne!= null) // this is a pointless if condition
_ligneBLL.InsetLigne(ligne);
}
The above if condition is pointless because the program execution will never enter the foreach loop if the collection is null.
I think you want to use a BindingList. It is the list I always use, but remember you will need to post your notifyChange events.
Related
Seems like a very basic MVVM question, but when it comes to Catel, I have issues doing it.
I have a property - registered as it should - which is a List, named Lines.
I bind it to a ListBox.
I also have a Button with a command adding a entry to Lines.
Lines is mapped to a model, and when I check the values of the model, I see it gets updated correctly when adding a value to Lines.
So everything seems to work, except that my view isn't updating when Lines is modified.
I tried to solve this by adding a RaisePropertyChanged("Lines") in Lines' setter, and in the command that adds a new value to Lines.
It gives something like this for the property:
[ViewModelToModel("MyModel", "Lines")]
public List<string> Lines
{
get { return GetValue<List<string>>(LinesProperty); }
set
{
SetValue(LinesProperty, value);
RaisePropertyChanged("Lines");
}
}
public static readonly PropertyData LinesProperty =
RegisterProperty("Lines", typeof(List<string>), null, (s, e) => {});
and this for the command (yes, I have AddLine = new Command(OnAddLineExecute); in the viewmodel's constructor):
public Command AddLine { get; private set; }
private async void OnAddLineExecute()
{
// this doesn't seem relevant, but since we're talking async and stuff, that may as well be the issue
if (!lineCountChecked && Lines.Count >= 4)
{
if (await messageService.Show(MainDialogs.LineCountCheck, "Lines count", MessageButton.OKCancel, MessageImage.Warning) != MessageResult.OK)
return;
else
lineCountChecked = true;
}
//
Lines.Add("New Line");
RaisePropertyChanged("Lines");
}
It's more than likely a very stupid mistake, but I can't get it. What did I miss? Thanks
You have 2 options to make this work:
1) RaisePropertyChanged(() => Lines) => will update the whole collection
2) Use ObservableCollection instead of List so the UI can actually respond to updates
I recommend 2.
I have a ListView with a ItemSource data binding and a SelectedItem data binding.
The ListView is populated with a new ItemSource every time I press the Next or Previous button.
The SelectedItem is updated accordingly, the items in the ItemSource have the Selected state, so it can be remembered when the user navigates back and forth.
While debugging, everything seems to work perfectly. The VM updates the controls as expected, and I can also see that the ListView has the correct selected value when I navigate with the next and previous buttons.
The problem is, that regardless of the fact that the ListView has a correct SelectedItem, the ListView does not visualize the SelectedItem as highlighted.
XAML:
<ListView
x:Name="_matchingTvShowsFromOnlineDatabaseListView"
Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
ItemsSource="{Binding AvailableMatchingTvShows}"
SelectedItem="{Binding AcceptedMatchingTvShow, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Behaviour in ViewModel responsible for repopulating the ItemSource and the SelectedItem:
private void UpdateForCurrentVisibleTvShow()
{
var selectedTvShow = FoundTvShows[CurrentTvShow];
// Update the available matches
var availableMatchingTvShows = new ObservableCollection<IWebApiTvShow>();
if (AvailableTvShowMatches[selectedTvShow] != null)
{
foreach (var webApiTvShow in AvailableTvShowMatches[selectedTvShow])
{
availableMatchingTvShows.Add(webApiTvShow);
}
}
AvailableMatchingTvShows = availableMatchingTvShows;
// Update the selected item
AcceptedMatchingTvShow = availableMatchingTvShows.FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
// Update the progress text
CurrentTvShowInfoText = string.Format(
"TV Show: {0} ({1} of {2} TV Shows)",
FoundTvShows[CurrentTvShow],
CurrentTvShow + 1,
FoundTvShows.Count);
// Update the AcceptedMatchingTvShow selection in the listview
OnPropertyChanged("AcceptedMatchingTvShow");
}
The implementation of AcceptedMatchingTvShow:
public IWebApiTvShow AcceptedMatchingTvShow
{
get
{
IWebApiTvShow acceptedTvShow = null;
if (FoundTvShows.Count > 0)
{
var tvShowName = FoundTvShows[CurrentTvShow];
acceptedTvShow = AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
}
return acceptedTvShow;
}
set
{
if (value != null)
{
var tvShowName = FoundTvShows[CurrentTvShow];
var currentlyAcceptedTvShow =
AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
if (currentlyAcceptedTvShow != null)
{
currentlyAcceptedTvShow.Accepted = false;
}
value.Accepted = true;
}
OnPropertyChanged();
}
}
I hope somebody can point me in the right direction. Just to be clear, the ListView has the correct items, and the SelectedItem is set with the correct item.
Well, I found 'a solution' to the problem after a lot of debugging and digging. I would REALLY like to understand if this is how WPF meant the control to behave, or if this is a bug in the ListViews data binding part. If anyone could tell me that, I am very very curious to the correct answer (and maybe I solved this problem in the wrong way, and somebody could explain me how I should've done this).
Anyway, the problem seems to be resolved when I create a copy of the object:
public IWebApiTvShow AcceptedMatchingTvShow
{
get
{
IWebApiTvShow acceptedTvShow = null;
if (FoundTvShows.Count > CurrentTvShow)
{
var tvShowName = FoundTvShows[CurrentTvShow];
acceptedTvShow = AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
}
if (acceptedTvShow != null)
{
// I MUST create a new instance of the original object for the ListView to update the selected item (why??)
return new WebApiTvShow(acceptedTvShow);
}
return null;
}
set
{
if (value != null)
{
var tvShowName = FoundTvShows[CurrentTvShow];
var availableTvShowMatch = AvailableTvShowMatches[tvShowName];
var currentlyAcceptedTvShow = availableTvShowMatch.FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
if (currentlyAcceptedTvShow != null)
{
currentlyAcceptedTvShow.Accepted = false;
}
value.Accepted = true;
}
OnPropertyChanged();
}
}
Note the call to the copy constructor :
return new WebApiTvShow(acceptedTvShow);
It works, but seems really ridiculous and smells like a bug in ListView to me. Is it?
I tried to explain the same problem in a simpler example here, if anybody can confirm the bug or can explain me how this should've been implemented I would greatly appreciate the insights.
A bit late to the game, but I had been jumping through hoops to solve this Problem in a similar setup. Setting the SelectedItem in a ListView using a bound Property in the Viewmodel or similar using a bound SelectedIndex just would not work. Until I tried to do it async:
Task.Factory.StartNew(() =>
{
BoundSelectedIndex = index;
});
Seems to work - more advanced contributors may answer why...
i know this is an old post but what worked is overriding the Equals and GetHashCode on your SelectedItem object so the listview can compare the SelectedItem with the bound collection
I am using a ListView control to display some lines of data. There is a background task which receives external updates to the content of the list. The newly received data may contain less, more or the same number of items and also the items itself may have changed.
The ListView.ItemsSource is bound to an OberservableCollection (_itemList) so that changes to _itemList should be visible also in the ListView.
_itemList = new ObservableCollection<PmemCombItem>();
_itemList.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
L_PmemCombList.ItemsSource = _itemList;
In order to avoid refreshing the complete ListView I do a simple comparison of the newly retrieved list with the current _itemList, change items which are not the same and add/remove items if necessary. The collection "newList" contains newly created objects, so replacing an item in _itemList is correctly sending a "Refresh" notification (which I can log by using the event handler OnCollectionChanged of the ObservableCollection`)
Action action = () =>
{
for (int i = 0; i < newList.Count; i++)
{
// item exists in old list -> replace if changed
if (i < _itemList.Count)
{
if (!_itemList[i].SameDataAs(newList[i]))
_itemList[i] = newList[i];
}
// new list contains more items -> add items
else
_itemList.Add(newList[i]);
}
// new list contains less items -> remove items
for (int i = _itemList.Count - 1; i >= newList.Count; i--)
_itemList.RemoveAt(i);
};
Dispatcher.BeginInvoke(DispatcherPriority.Background, action);
My problem is that if many items are changed in this loop, the ListView is NOT refreshing and the data on screen stay as they are...and this I don't understand.
Even a simpler version like this (exchanging ALL elements)
List<PmemCombItem> newList = new List<PmemCombItem>();
foreach (PmemViewItem comb in combList)
newList.Add(new PmemCombItem(comb));
if (_itemList.Count == newList.Count)
for (int i = 0; i < newList.Count; i++)
_itemList[i] = newList[i];
else
{
_itemList.Clear();
foreach (PmemCombItem item in newList)
_itemList.Add(item);
}
is not working properly
Any clue on this?
UPDATE
If I call the following code manually after updating all elements, everything works fine
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
But of course this causes the UI to update everything which I still want to avoid.
After a change, you can use the following to refresh the Listview, it's more easy
listView.Items.Refresh();
This is what I had to do to get it to work.
MyListView.ItemsSource = null;
MyListView.ItemsSource = MyDataSource;
I know that's an old question, but I just stumbled upon this issue. I didn't really want to use the null assignation trick or the refresh for just a field that was updated.
So, after looking at MSDN, I found this article:
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netframework-4.7.2
To summarize, you just need the item to implement this interface and it will automatically detect that this object can be observed.
public class MyItem : INotifyPropertyChanged
{
private string status;
public string Status
{
get => status;
set
{
OnPropertyChanged(nameof(Status));
status = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
So, the event will be called everytime someone changes the Status. And, in your case, the listview will add a handler automatically on the PropertyChanged event.
This doesn't really handle the issue in your case (add/remove).
But for that, I would suggest that you have a look at BindingList<T>
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.bindinglist-1?view=netframework-4.7.2
Using the same pattern, your listview will be updated properly without using any tricks.
You should not reset ItemsSource of ListView each time observable collection changed. Just set proper binding that will do your trick. In xaml:
<ListView ItemsSource='{Binding ItemsCollection}'
...
</ListView>
And in code-behind (suggest to use MVVM) property that will be responsible for holding _itemList:
public ObservableCollection<PmemCombItem> ItemsCollection
{
get
{
if (_itemList == null)
{
_itemList = new ObservableCollection<PmemCombItem>();
}
return _itemList;
}
}
UPDATE:
There is similar post which most probably will Answer your question: How do I update an ObservableCollection via a worker thread?
I found a way to do it. It is not really that great but it works.
YourList.ItemsSource = null;
// Update the List containing your elements (lets call it x)
YourList.ItemsSource = x;
this should refresh your ListView (it works for my UAP :) )
An alternative on Xopher's answer.
MyListView.ItemsSource = MyDataSource.ToList();
This refreshes the Listview because it's a other list.
Please check this answer:
Passing ListView Items to Commands using Prism Library
List view Items needs to notify about changes (done is setter)
public ObservableCollection<Model.Step> Steps
{
get { return _steps; }
set { SetProperty(ref _steps, value); }
}
and UpdateSourceTrigger need to be set in xaml
<Image Source="{Binding ImageData, UpdateSourceTrigger=PropertyChanged}" />
I have a Property which is of List Type as below.
private List<ListCOP_PAYLOAD> m_COP_DATA = new List<ListCOP_PAYLOAD>();
public List<ListCOP_PAYLOAD> COP_DATA
{
get
{
return m_COP_DATA;
}
set
{
m_COP_DATA = value;
FireNewSMode_Data();
}
}
Each time I add data I use the following:
m_exchangedata.COP_DATA = Mstruct.lMCOPStruct;
But it just increases the list size with the new data variables.
I want to clear the previous data so I used
m_exchangedata.COP_DATA.Clear();
Edited:
I use it in this order
m_exchangedata.COP_DATA.Clear();
m_exchangedata.COP_DATA = Mstruct.lMCOPStruct;
but that does not seem to work.
I get InvalidOperationException : Sequence contains no elements.
The FireNewSMode_Data just fires off an event with the data if the event handler is not null.
Any help would be appreciated.
Thanks in advance.
Put a condition of count before clearing the COP_DATA list:
if(m_exchangedata.COP_DATA.Count > 0)
{
m_exchangedata.COP_DATA.Clear();
}
m_exchangedata.COP_DATA = Mstruct.lMCOPStruct;
Using WPF C#.NET4.5 using visual studio 2012 ulti.
Old winforms code:
foreach (DataGridViewRow paretoRow in ParetoGrid.Rows)
{
if ((Convert.ToInt32(paretoRow.Cells["CurrentPareto"].Value) < (Convert.ToInt32(paretoRow.Cells["NewPareto"].Value))))
{
paretoRow.Cells["pNew"].Value = downArrow
}
}
As you can see each row I cycle through I check a specific cell, if true I then populate another cell. This was good old winforms code I used many times before...however.
Switching over to WPF was alot more different than i previously assumed.
DataGrid does not contain the Row property. Instead, I think you need to use:
DataGridRow paretoRow in paretogrid.Items
But im still at a loss on who to now get the cell.
So my question is, is there syntax changes to perform, if so where? Or as I'm beginning to believe datagrids in WPF operate with Objects more so than winforms thus not needing to use a propertie called "row", if this is the case what logic/syntax should i know use in this example?
Thanks for your patience guys, think when I go home for the bank holiday I'll do a bit of WPF digging to see how different it actually is.
People seem to be overcomplicating this, this worked for me:
foreach (System.Data.DataRowView dr in yourDataGrid.ItemsSource)
{
MessageBox.Show(dr[0].ToString());
}
I think first think you want to do is to get all rows of your DataGrid:
public IEnumerable<Microsoft.Windows.Controls.DataGridRow> GetDataGridRows(Microsoft.Windows.Controls.DataGrid grid)
{
var itemsSource = grid.ItemsSource as IEnumerable;
if (null == itemsSource) yield return null;
foreach (var item in itemsSource)
{
var row = grid.ItemContainerGenerator.ContainerFromItem(item) as Microsoft.Windows.Controls.DataGridRow;
if (null != row) yield return row;
}
}
and then iterate through your grid:
var rows = GetDataGridRows(nameofyordatagrid);
foreach (DataGridRow row in rows)
{
DataRowView rowView = (DataRowView)row.Item;
foreach (DataGridColumn column in nameofyordatagrid.Columns)
{
if (column.GetCellContent(row) is TextBlock)
{
TextBlock cellContent = column.GetCellContent(row) as TextBlock;
MessageBox.Show(cellContent.Text);
}
}
Yes, you are right. WPF DataGrid is built around better supporting the use of objects.
You could use a ViewModel similar to the following. Build them all into a collection and then set that collection as your ItemsSource. You would also need to use a ValueConverter if you want to display and image instead of a checkmark for pNew being true/false.
public class FooViewModel : INotifyPropertyChanged
{
private int currentPareto;
public int CurrentPareto
{
get
{
return currentPareto;
}
set
{
if (currentPareto == value)
return;
currentPareto = value;
OnPropertyChanged("CurrentPareto");
OnPropertyChanged("pNew");
}
}
private int newPareto;
public int NewPareto
{
get
{
return newPareto;
}
set
{
if (newPareto == value)
return;
newPareto = value;
OnPropertyChanged("NewPareto");
OnPropertyChanged("pNew");
}
}
public bool pNew
{
get
{
return CurrentPareto < NewPareto;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Edit
To simplify it a little, you could use a base ViewModel class and use PropertyChanged weaving. The code would simplify to this:
public class FooViewModel : ViewModelBase
{
public int CurrentPareto { get; set; }
public int NewPareto { get; set; }
public bool pNew { get { return CurrentPareto < NewPareto; } }
}
I don't even understand why is it just so complicated to get rows and their values in a datagrid. It feels like hell finding how. The api even give funny funny event names which is not so direct to the point also. Why can't just people concentrate on the baseline and give what exactly is needed and not all sorts of different options with no use and sense at all. I mean to eat all you need is a spoon and fork right. Never even changed since 100,000 years ago. This is my code thanks to the guy who mentioned some people just try to over-complicate things and waste your time.
private void dtaResultGrid_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ActivateTestDatagridAccess();
}
public async void ActivateTestDatagridAccess()
{
try
{
await Task.Delay(500);
foreach (System.Data.DataRowView dr in dtaResultGrid.ItemsSource)
{
for (int j = 0; j < dtaResultGrid.Columns.Count; j++)
{
Console.WriteLine(dr[j].ToString());
}
Console.Write(Environment.NewLine);
}
}
catch (Exception exrr)
{
Console.WriteLine(exrr.ToString());
}
}
The 'simplest' answer, from Charles, did it for me. But I used Items instead of ItemsSource.
Now, for people getting this error:
System.InvalidCastException
Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.Data.DataRowView'.
What did it for me was disabling the DataGrid's property CanUserAddRows. This removes the placeholder line for a new line, thus removing the placeholder object (which is NOT a DataRowView, but something else). If you already have this disabled, then I don't know.
Since I wanted to loop through each element of each row, I added another foreach:
foreach (System.Data.DataRowView dr in nameofyourgrid.Items)
{
foreach (var item in dr.Row.ItemArray)
{
MessageBox.Show(item.ToString());
}
}
In WPF you go about it a lot more dynamic and ObjectOrientated. You can bind the Column "pNew" on a Property of the element you put in the DataGrid, which returns downarrow.
If the value changes you can raise the Event PropertyChanged (Interface INotifyPropertyChanged) and the bound Property will get reevaluated.
Also interesting for beginning with WPF is DataTemplate, ControlTemplate, Converter.
Converter changes the Property Value to a usable Value for WPF (e.g. BoolToVisibility) when the Property gets called.
DataTemplate and ControlTemplate can be used to alter the appearance of the Control.
There are several good Tutorials for WPF out there. I would also recommend to look into the MVVM-Pattern to use as a between layer of your Businessobject and your WPF-Control, especially to handle things like what you try to do here.
if you fill your datagridview rows using an instance of a class (like struct_class)
this would be the fastest way to have a foreach loop
foreach (struct_class row in dgv.Items)
{
MessageBox.Show(row.name);
}
Why can't you just use this property to get the number of rows and then use a For loop to iterate through?
dataGridView1.Rows.Count