I'm kind of new to programming and come to quite of a problem.
I'm sure the solution is quite simple but I just can't get my head around it.
I just want to know how to Delete from the listbox and delete the data from the list entirely, because whenever I delete from the list box it disappears but when I add a new layer all the layers that I have deleted come back (so I'm guessing it doesn't really delete from the list?.
This is at the start of my code at the top:
List<Layer> layers = new List<Layer>();
This is my Layers Class:
public class Layer
{
private Image mLayerData = null;
private string mLayerName = "Layer";
public string LayerName
{
get { return mLayerName; }
set { mLayerName = value; }
}
public Image LayerData
{
get { return mLayerData; }
}
public Layer(int width = 500, int height = 500, string layername = "Layer")
{
mLayerData = new Bitmap(width, height);
mLayerName = layername;
}
}
and this is my addLayer Function:
private void addNewLayer()
{
string layerName = "Layer";
layerName += layers.Count;
// Create a default layer in our stack of layers
layers.Add(new Layer(pictureBox1.Width, pictureBox1.Height, layerName));
// Make the picture box talk to the default layer
pictureBox1.Image = layers[0].LayerData;
pictureBox1.Invalidate();
// Update the list of layers
listLayers.Items.Clear();
foreach(Layer l in layers)
{
listLayers.Items.Add(l.LayerName);
}
listLayers.SelectedIndex = listLayers.Items.Count - 1;
}
for my deleteLayer function I have this:
private void deleteLayer()
{
listlayers.Items.RemoveAt(listlayers.SelectedIndex);
}
A better approach would be to use BindingList, which is supports Data Binding and use DataSource property of Listbox to bind the collection.
For example,
BindingList<Layer> layers = new BindingList<Layer>();
listBox.DataSource = layers;
listBox.DisplayMember = nameof(Layer.LayerName);
Also, note that instead of binding/add names of Layer to the Listbox, you should instead bind the Collection of Layer and use the DisplayMember property to ensure the LayerName is displayed in the Listbox.
Now you could Add to the listbox as the following
var layer = new Layer(pictureBox1.Width, pictureBox1.Height, layerName);
layers.Add(newLayer);
Remove
layers.Remove((Layer)listBox.SelectedItem);
The BindingList would refresh the Listbox by itself.
Related
After my balance label is initially bound to a number, changing the datasource again doesn't update the value again.
I want to update the a Windows Form Label automatically after the database object is changed and I re-pull it into the constructorData.BankAccount.
public class ConstructorData
{
public Client Client { get; set; }
public BankAccount BankAccount { get; set; }
}
private void frmTransaction_Load(object sender, EventArgs e)
{
// Pretend we populated constructor data already
// This line of code is working
bankAccountBindingSource.DataSource = constructorData.BankAccount;
}
private void lnkProcess_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
constructorData.BankAccount = db.BankAccounts.Where(x => x.BankAccountId == constructorData.BankAccount.BankAccountId).SingleOrDefault();
// What do I do here
// Doesn't work
bankAccountBindingSource.EndEdit();
bankAccountBindingSource.ResetBindings(false);
}
Auto generated code:
//
// lblAccountBalance
//
this.lblAccountBalance.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.lblAccountBalance.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.bankAccountBindingSource, "Balance", true));
this.lblAccountBalance.Location = new System.Drawing.Point(482, 71);
this.lblAccountBalance.Name = "lblAccountBalance";
this.lblAccountBalance.Size = new System.Drawing.Size(196, 23);
this.lblAccountBalance.TabIndex = 7;
this.lblAccountBalance.Text = "label1";
Since here (inside the form load):
bankAccountBindingSource.DataSource = constructorData.BankAccount;
you bind directly to the BankAccount instance, even implementing INotifyPropertyChanged in ConstructorData class (as suggested in the comments) will not help.
With that design, anytime you assign a new BankAccount instance to the ConstructorData.BankAccount property (as in the shown code), you need also to set it as DataSource of the BindingSource used:
constructorData.BankAccount = db.BankAccounts.Where(x => x.BankAccountId == constructorData.BankAccount.BankAccountId).SingleOrDefault();
// What do I do here
bankAccountBindingSource.DataSource = constructorData.BankAccount;
Without Implementing INotifyPropertyChanged Ivan's answer is exactly what you need.
The reason is because you put an object in DataSource of binding source this way: BindingSource.DataSource = constructorData.BankAccount, so it uses the object which is in BankAccount property as data source. If you change the value of constructorData.BankAccount, you disd't changed the data source of BindingSource and it will contain the previous object. For example take a look at this code:
var a = new MyClass("1"); // ← constructorData.BankAccount = something;
var b = a; // ← bindingSource.DataSource = constructorData.BankAccount.
a = new MyClass("2"); // ← constructorData.BankAccount = something else;
What should contain b now? Do you expect b contains MyClass("1")? Surely no.
For more information take a look at this post:
Data Binding doesn't work when I assign a new object instance to the bound variable
Can I use INotifyPropertyChanged to solve the problem?
If you implement INotifyPropertyChanged in ConstructorData and change bindings this way, yes:
bankAccountBindingSource.DataSource = constructorData;
//...
this.lblAccountBalance.DataBindings.Add(new System.Windows.Forms.Binding("Text",
this.bankAccountBindingSource, "BankAccount.Balance", true));
I have a problem with a ListView. I want each Cell to have a label and a switch but the text of the label does not appear.
Here is my code:
public class FilterPage : ContentPage
{
public FilterPage()
{
List<FilterCell> listContent = new List<FilterCell>();
foreach(string type in Database.RestaurantTypes)
{
FilterCell fc = new FilterCell();
fc.Text = type;
listContent.Add(fc);
}
ListView types = new ListView();
types.ItemTemplate = new DataTemplate(typeof(FilterCell));
types.ItemsSource = listContent;
var layout = new StackLayout();
layout.Children.Add(types);
Content = layout;
}
}
public class FilterCell : ViewCell
{
private Label label;
public Switch CellSwitch { get; private set; }
public String Text{ get { return label.Text; } set { label.Text = value; } }
public FilterCell()
{
label = new Label();
CellSwitch = new Switch();
var layout = new StackLayout
{
Padding = new Thickness(20, 0, 0, 0),
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.FillAndExpand,
Children = { label, CellSwitch }
};
View = layout;
}
}
If I enter a fixed Text in the FilterCell-Constructor it works fine (e.g.: label.Text = "Hello World")
When I create a Method for the ItemSelected-Event and read out the SelectedItem.Text Property I get the text I assigned as Value but it's never displayed. Only the switch is displayed when I try to run this Code.
Thanks for your help
Niko
Ohh boy. This code looks like a rape (sorry I had to say this).
Now let's see what's wrong:
The reason is you are mixing up data and view heavily.
The line
types.ItemTemplate = new DataTemplate(typeof(FilterCell));
means: "For each item in the list (ItemsSource) create a new filter cell". The FilterCells that you create in the loop are never displayed.
The easy fix
public class FilterPage : ContentPage
{
public FilterPage()
{
var restaurantTypes = new[] {"Pizza", "China", "German"}; // Database.RestaurantTypes
ListView types = new ListView();
types.ItemTemplate = new DataTemplate(() =>
{
var cell = new SwitchCell();
cell.SetBinding(SwitchCell.TextProperty, ".");
return cell;
});
types.ItemsSource = restaurantTypes;
Content = types;
}
}
There is a standard cell type that contains a label and a switch SwitchCell, use it.
As ItemsSource of your list, you have to use your data. In your case the list of restaurant types. I just mocked them with a static list.
The DataTemplate creates the SwitchCell and sets the Databinding for the Text property. This is the magic glue between View and data. The "." binds it to the data item itself. We use it, because our list contains items of strings and the Text should be exactly the string. (read about Databinding: https://developer.xamarin.com/guides/xamarin-forms/getting-started/introduction-to-xamarin-forms/#Data_Binding )
I striped away the StackLayout that contained the list. You can directly set the list as Content of the page.
Lesson
use standard controls, if possible
You should always try to remember to keep data and view apart from each other and use data binding to connect to each other.
Try to avoid unnecessary views.
I have problem with ordering data for ListView. I have EventDisplay class which is an ObservableCollection for ListView(called Events)
private ObservableCollection<EventDisplay> currentEvents = new ObservableCollection<EventDisplay>();
private void Events_Loaded(object sender, RoutedEventArgs e)
{
sv = (ScrollViewer)VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(this.Events, 0), 0);
Events.ItemsSource = currentEvents;
}
I then add new data by function :
private void LoadDataToList(List<EventDisplay> newItems)
{
foreach (EventDisplay ed in newItems)
{
//Set some additional data
currentEvents.Add(ed);
}
//When this line below is commented ListView data is updated
//but is not sorted, when i uncomment the listview data is not being updated
//currentEvents = new ObservableCollection<EventDisplay>(currentEvents.OrderByDescending(x => x.ed.date).ToList());
}
So what is the proper way of ordering data for ListView in Windows 8.1 apps ?
You can sort & filter the view of your ObservableCollection (explanation here)
public class ViewableCollection<T> : ObservableCollection<T>
{
private ListCollectionView _View;
public ListCollectionView View
{
get
{
if (_View == null)
{
_View = new ListCollectionView(this);
}
return _View;
}
}
}
Data structure for the example:
interface ICustomer
{
string CuctomerName{get;set;}
int Age{get;set;}
}
Example use of the code:
ViewableCollection<ICustomer> vCustomers = new ViewableCollection<ICustomer>();
// Sorting settings:
ViewableCollection<ICustomer> vCustomers.View.SortDescriptions.Add(new SortDescription("CustomerName", ListSortDirection.Ascending));
vCustomers.View.Filter = MyCustomFilterMethod;
// add data to collection
MyCustomers.ToList().ForEach(customer => vCustomers.Add(customer));
Examlpe filter method:
private bool MyCustomFilterMethod(object item)
{
ICustomer customer = item as ICustomer;
return customer.Age > 25;
}
when you need to refresh the filter, the only thing you need to do is call:
this.vCustomers.View.Refresh();
Then you bind your GUI to vCustomers.View
You don't need to reset binding sources etc.
Use this for your add items code:
foreach (EventDisplay ed in newItems.OrderByDescending(x => x.ed.date).ToList()
{
//Set some additional data
currentEvents.Add(ed);
}
The reason your doesn't work is that you are reassigned the currentEvents reference rather than updating the ObservableCollection.
You should do the following :
currentEvents = new ObservableCollection<EventDisplay>(currentEvents.OrderByDescending(x => x.ed.date).ToList());
Events.ItemsSource = currentEvents;
This forces the ListView to rebind to your new sorted observable collection.
Another option is to sort the Observable collection in place. However, it may introduce flickering as the ListView will constantly update as the sort progresses.
If you don't want the ScrollView to reset its position, you can save the scrollview position and then restore it after sorting the list.
I've had success with Implementing a custom ObservableCollection that supports sorting but prevents UI flickering by suspending change notification during sort and then issuing a reset notification. The ScrollView should stay at its current position even when confronted with the reset event.
I have the following two classes for a RageComic reader metro style app I am making.
public class ComicDataWNGroup
{
public ComicDataWNGroup(string title)
{
this.Title = title;
this.Items = new ObservableCollection<ComicDataItem>();
}
public String Title { get; set; }
public ObservableCollection<ComicDataItem> Items { get; private set; }
}
public class ComicDataWNSource
{
private RedditAPI.RedditManager currentManager;
public ComicDataWNSource(RedditAPI.RedditManager currentManager)
{
this.currentManager = currentManager;
Groups = new ObservableCollection<ComicDataWNGroup>();
}
public async Task<int> ObtainRecentContent()
{
SubredditComicDataSource comicList = new SubredditComicDataSource();
await comicList.LoadItems();
var comicSources = comicList.GetAllItems();
for (int i = 0; i < comicSources.Count; i++ )
{
var links = await this.currentManager.GetSubredditLinks(comicSources[i].UrlTitle, 6, 0);
ComicDataWNGroup group = new ComicDataWNGroup("From " + comicSources[i].Title);
for(int j = 0; j < links.Data.Count; j++)
{
var linkItem = (RedditAPI.Objects.Link)links.Data[j];
BitmapImage img = await ThumbnailRetriever.GetThumbnailFromRedditUrl(linkItem.ThumbnailUrl);
ComicDataItem item = new ComicDataItem(linkItem.Title, false, true, 0, img, 0, null, null, Visibility.Visible, Visibility.Collapsed);
group.Items.Add(item);
}
Groups.Add(group);
}
return 0;
}
public ObservableCollection<ComicDataWNGroup> Groups
{
{ get; private set; }
}
}
Note: ComicDataItem is a class that mostly contains get set properties. I can post it later if it is needed.
Using these two classes, I am trying to bind the Groups property of the ComicDataWNSource object to a GridView.
The following code is the code I use for the binding.
DataModel.ComicDataWNSource source = new DataModel.ComicDataWNSource(App.Manager);
await source.ObtainRecentContent();
CollectionViewSource viewSource = new CollectionViewSource();
viewSource.Source = source.Groups;
viewSource.IsSourceGrouped = true;
viewSource.ItemsPath = new PropertyPath("Items");
this.itemGridView.ItemsSource = viewSource;
The GridView is the default one generated by Visual Studio when creating a new Grouped Items Page. I already changed the appropriate bindings to match up with the properties in the ComicDataWNGroup class and ComicDataItem class (I can post the XAML for the GridView as well if needed). Also, source.ObtainRecentContent() is currently filling up the Groups array with the correct data. The array of Groups and inner array of Items both have objects in them and neither of them are null.
The problem arises on the line setting the view source to the grid view. Whenever I try it, it throws the following exception:
Argument Exception: Value does not fall within the expected range.
I don't have a clue what I could be doing wrong for this to happen. I already looked at this msdn page already, which shows you how to use a grouped grid view item and view source. Any ideas what I'm doing wrong?
Ok, I managed to make this work by doing the following:
In the xaml where my GridView is defined, I declared the CollectionViewSource in the same xaml file instead of the code behind file.
<Page.Resources>
<CollectionViewSource x:Name="itemsViewSource" ></CollectionViewSource>
</Page.Resources>
I bound the ItemsSource property of the GridView with the following property:
<GridView
x:Name="itemGridView"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
</GridView>
In the code behind, I changed the code to the following:
DataModel.ComicDataWNSource source = new DataModel.ComicDataWNSource(App.Manager);
await source.ObtainRecentContent();
itemsViewSource.Source = source.Groups;
itemsViewSource.IsSourceGrouped = true;
this.itemsViewSource.ItemsPath = new PropertyPath("Items");
By making these changes, the code works. I'm now sure the thing I was doing wrong is that ItemsSource expects a Binding object which points to the CollectionViewSource object, not the actual CollectionViewSource object.
I'm using WPF to create an application to enable an organisation to enter different pieces of data into the application.I have a tab control to allow them to do this.
Then in a separate view, I have a series of different data grids showing the user what data they have inserted into the database. Containing buttons to either, add, update or delete the data they want.
Which leads me to my question. Currently, I am able to delete, and add data with ease and with no problem. But then comes my issue with trying to get the selected item to update, which it doesn't, resulting in a null reference exception.
If i set my property attributes programmatically though, it updates it fine. like so;public int _OrganisationTypeDetailID = 17; public int _OrganisationTypeID = 1;But I do not want this, as I want the ability for the user to select for themselves and update the data they need to.
Here's some of the code that may help in resolving my issue;
View Model;
public void UpdateOrganisationTypeDetail(OrganisationTypeDetail orgTypeDetail)
{
using (DBEntities context = new DBEntities())
{
var orgTD = context.OrganisationTypeDetails.Where(otd => otd.OrganisationTypeDetailID == SelectedType.OrganisationTypeDetailID).FirstOrDefault();
if (orgTD != null)
{
orgTD.Title = Title;
orgTD.FirstName = FirstName;
orgTD.Surname = Surname;
orgTD.Position = Position;
orgTD.DateOfBirth = DateOfBirth;
orgTD.Address = Address;
orgTD.Country = Country;
orgTD.Postcode = Postcode;
orgTD.PhoneNumber = PhoneNumber;
orgTD.MobileNumber = MobileNumber;
orgTD.FaxNumber = FaxNumber;
orgTD.Email = Email;
orgTD.NINumber = NINumber;
//context.OrganisationTypeDetails.Attach(orgTD);
context.OrganisationTypeDetails.ApplyCurrentValues(orgTD);
context.SaveChanges();
MessageBox.Show("Updated Organisation Type Details");
}
else
{
MessageBox.Show("Unable to update selected 'Type'.");
}
}
private OrganisationTypeDetail _SelectedType;
public OrganisationTypeDetail SelectedType
{
get
{
return _SelectedType;
}
set
{
if (_SelectedType == value)
return;
_SelectedType = value;
OnPropertyChanged("SelectedType");
}
}
public List<OrganisationTypeDetail> GetOrganisationTypeDetail //Loads data
{
get
{
using (DBEntities context = new DBEntities())
{
var query = from e in context.OrganisationTypeDetails
select e;
return query.ToList<OrganisationTypeDetail>();
}
}
}
private ICommand showUpdateCommand;
public ICommand ShowUpdateCommand //Update command
{
get
{
if (showUpdateCommand == null)
{
showUpdateCommand = new RelayCommand(this.UpdateFormExecute, this.UpdateFormCanExecute); //i => this.UpdateOrganisationTypeDetail()
}
return showUpdateCommand;
}
}
Code behind;
private void btnUpdateOrgTypeDetail_Click(object sender, RoutedEventArgs e)
{
OrganisationTypeDetail selected = dgOrgTypeDetail.SelectedItem as OrganisationTypeDetail;
OrganisationTypeDetailViewModel org = new OrganisationTypeDetailViewModel();
if (selected == null)
MessageBox.Show("You must select a 'Type' before updating.");
else
{
OrganisationTypeDetailUpdateView update = new OrganisationTypeDetailUpdateView();
update.ShowDialog();
org.UpdateOrganisationTypeDetail(selected);
Page_Loaded(null, null);
}
}
xaml;
<DataGrid Name="dgOrgTypeDetail" Height="145" Width="555"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding GetOrganisationTypeDetail}"
SelectedItem="{Binding SelectedType, Mode=TwoWay}">
Hope this issue can be resolved.
I would say that your best bet for this is to use commanding in the MVVM pattern to achieve this..
It looks like you're using a combination of MVVM and code behind and actually creating a new instance of the view model when your click event fires. Try binding the view model to your view once in the code behind of the view as the datacontext and then try updating the selected type..
Also when you're trying to do the update on SelectedType - look at your View using Snoop - see if the SelectedType property is still bound to the view.
ICommand UpdateOrgTypeDetail { get;}
Then in the view model constructor declare new instance
UpdateOrgTypeDetail = new DelegateCommand<object>(ExecuteUpdateOrgTypeDetail, CanExecuteUpdateOrgTypeDetail);
These two delegates will then allow you to click your button (which needs to bind to UpdateOrgTypeDetail)
<Button Command="{Binding UpdateOrgTypeDetail}" />
You should find that the update on the property is done correctly from here.