Problem calling labelname.Text in my source code - c#

I am very new to WPF in C# and I am stuck working on an application made to (among other things) calculate the total number of items on a list. I want to print a label with that number/the sum and in tutorials I have seen people call labelname.Text in their source code, but when I try to do the same I don't have that option.
My Windows application design
It is in Danish, but what I need is the sum of "Antal" from the table printed on a label next to "Samlet antal varer:" to the right of the table.
My DataGrid:
<DataGrid ColumnWidth="*" CanUserAddRows="False" x:Name="StockList" Margin="57,23,453,261">
<!--Column Header Text & Bindings -->
<DataGrid.Columns>
<DataGridTextColumn Header="Vare" Binding="{Binding vare}" Width="auto"/>
<DataGridTextColumn Header="Pris" Binding="{Binding pris}" Width="50"/>
<DataGridTextColumn Header="Antal" Binding="{Binding antal}" Width="50"/>
<DataGridTextColumn Header="Samlet pris" Binding="{Binding samletPris}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
My Labels:
<!-- labels -->
<Label x:Name="lblSamletVarer" HorizontalAlignment="Left" Margin="637,142,0,0" VerticalAlignment="Top"/>
<Label x:Name="lblSamletPrisEkskl" HorizontalAlignment="Left" Margin="637,192,0,0" VerticalAlignment="Top"/>
<Label x:Name="lblSamletPrisInkl" HorizontalAlignment="Left" Margin="637,242,0,0" VerticalAlignment="Top"/>
I hope it all makes sense. Please reach out if you need more information in order to help.

Try
<TextBlock x:Name="Label1" Text="{Binding Path=Label1Value}"/>
in XAML to replace your label markup (a better option than a Label) then provide a value for it in your ViewModel or code-behind; you can provide a literal value as I've shown below or use whatever logic you like to compute a value at runtime and return that. A TextBlock uses a OneWay binding by default since it is read-only, so you don't need to provide a setter or PropertyChanged notification.
public string Label1Value
{
get { return "This string in your TextBlock"; }
}
Obviously, use whatever more appropriate identifiers you like for "Label1Value" and "Label1"!
It isn't clear what you are trying to bind the DataGridTextColumn elements to in your code. You can bind directly from one control to another using the "ElementName" syntax within the binding expression, but since you need to compute something from your DataGridTextColumn, I assume you'll bind that to something in your ViewModel or code-behind and calculate the value there before passing it back.

Related

The solution given for "ObservableCollection not updating View" doesn't work

I have a DataGrid that binds to an ObservableCollection named Programs. On the window there's 2 buttons, one for changing a bit field in the selected row to Activate, the other to change it to Deactivate. Since we're using .EDMX files, so don't have access to the generated C# code for each model class, changing a value of the bit field in one of the rows of Programs doesn't change the value in the DataGrid. I understand that. I looked up here on SO how I might be able to do this. I found a post from almost 8 years ago, titled ObservableCollection not updating View. This looked very promising so I implemented the solution given by aqwert. However, it still is not working. I know the value is getting modified and following aqwert's solution I'm replacing Programs. Doesn't matter, it doesn't update the view.
We're using .NET Framework 4.5.2. We're also using MVVM Light. And we're using FirstFloor Software's ModernUI for WPF.
Here's the XAML for the DataGrid:
<DataGrid
Grid.Row="3"
Grid.Column="2"
AutoGenerateColumns="False"
BorderThickness="1"
CanUserAddRows="False"
ItemsSource="{Binding Programs}"
SelectedItem="{Binding SelectedProgram, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<DataGrid.Columns>
<DataGridTemplateColumn
Width="Auto"
Header="ID"
IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ID}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn
Width="Auto"
Header="Abbrev"
IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProgramAbbrev}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn
Width="Auto"
Header="Program Name"
IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProgramName}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn
Width="Auto"
Header="Inactive"
IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Inactive, Converter={StaticResource BoolToYN}}" TextAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
Here's the definition of Programs from the VM:
private ObservableCollection<Program> programs;
public ObservableCollection<Program> Programs
{
get { return programs; }
set
{
if (programs != value)
{
programs = value;
RaisePropertyChanged("Programs");
}
}
}
and lastly, here's the code that I've made, following aqwert's solution:
//make a copy of Programs
var programsCopy = new List<Program>();
foreach (var item in Programs)
{
if (item.ID == SelectedProgram.ID)
{
item.Inactive = inactiveFlag;
item.UpdatedBy = Environment.UserDomainName + "\\" + Environment.UserName;
item.UpdatedOn = rightNow;
}
programsCopy.Add(item);
}
//copy over the top of Programs
Programs = new ObservableCollection<Program>(programsCopy);
An ObservableCollection<T> doesn't raise change notifications to the UI when properties of individual items in the collection are modified. The way to do this is to implement the INotifyPropertyChanged event in your Program class.
If you can't modify Program for whatever reason, you could create a new view model class that wraps it and change the type of your source collection property from ObservableCollection<Programs> to IEnumerable<ProgramViewModel>. You don't need an ObservableCollection if you reset the Programs property to a new instance each time you want to change the collection.
You then implement the INotifyPropertyChanged in the ProgramViewModel class and bind to properties of this one.
I finally discovered my problem. Stupid error on my part. I have 2 routines, once each that are bound to two different ICommands. One for the Activate button, the other for the Deactivate button. I'll just should the Deactivate button's code:
private void ExecuteDeActivateCommand()
{
SetInactiveFlag(true);
GetProgramsSynchronously(SelectedRow.ID); //this call was what was causing me problems
}
Yesterday I showed you some code from the SetInactiveFlag(true) call. That worked fine and was setting the Inactive bit flag to true. However, I'd forgotten that I'd called the GetProgramsSynchronously(SelectedRow.ID) call would go against the database and refresh the in-memory Programs collection, with the Inactive flag set back to false.
Egg on my face. I want you all to learn from my mistake so hopefully you have avoid egg on your face.

How to display a List of Strings as a DataGrid (table) in XAML and C#

I've been a long time lurker on SO and have only recently decided to set up an account. I've been spending quite a lot of hours trying to solve this problem I've been having without asking but I here it is.
What I'm trying to accomplish:
I have a list of strings, e.g: Mango, Banana, Melon (let's call it fruits) and I want to display it as a table in XAML (WPF), where it appears as row values on the left side, and the right side will be combo boxes that will allow users to pick a value. I'm currently stuck on the displaying part of this problem. I decided to use a DataGrid to display the list, and the code behind will do the data bindings.
Here's the XAML part of it:
<DataGrid x:Name="FruitDataGrid" Height="265" VerticalAlignment="Center" Margin="-7,8,-2,-6" HorizontalAlignment="Left" Width="1188" AutoGenerateColumns="False">
<!-- If AutoGenerateColumns was true only the length is displayed.-->
<DataGrid.Columns>
<DataGridTextColumn x:Name="fruitsDisplay" Header="Fruits" MinWidth="450" IsReadOnly="True" />
<DataGridComboBoxColumn Header="Number of Boxes" MinWidth ="200" CanUserResize="True" Width="*"></DataGridComboBoxColumn>
</DataGrid.Columns>
Code Behind so far has been really simple, and after many attempts this is the most recent one.
private void populateFruitList()
{
FruitDataGrid.DataContext = fruitDataTable;
//Binds a datatable instance to the datagrid, does not display anything.
}
What I've been attempting:
Turn the List of Strings into an Observable Collection, or Enumerable, and do FruitDataGrid.itemsource = fruitsObservable;
The above method works, except that it will only display the length of each string value (if autogeneratecolumns is true), which is not what I want. If autogeneratecolumns was false then I can see the rows being shown, but not the string values.
I also tried using DataView or DataGridView but I failed to define it in the XAML, VS2012 says that there isn't such a thing.
I've been trying to do data binding as well as the MSDN Article says, but VS2012 never manages to bind to the list properly.
I then attempted to change my list into a datatable and [use a datagridview as specified here but again the XAML tells me it is not a valid class][2]. I am also aware of the performance impact datagridview might have but at this point I just want to display a table really.
I've also seen that some tutorials use a DataGrid.datasource method but that isn't in the DataGrid I want, I think it's a different class? (the DataGrid I am attempting to use is System.Windows.Controls.DataGrid, and the other one is in Windows Forms [As shown here][3])
Thanks again for taking the time to look into this.
EDIT
Trying to bind to the list in XAML this way:
<DataGrid x:Name="FruitDataGrid" Height="265" VerticalAlignment="Center" Margin="-7,8,-2,-6" HorizontalAlignment="Left" Width="1188" AutoGenerateColumns="False" ItemsSource="fruitDataList">
I get the error in XAML "The type converter for IEnumerable does not support converting from a string" which I think is because I'm doing it wrong. The table now shows a lot of empty rows though.
Trying ItemSource="{Binding fruitDataList}" (where fruitDataList is a List) yields a blank table, and an error in VS for BindingExpression path error.
To sum up what the previous issue was, thanks to Muds, and hours of trying, the Binding in the XAML was not properly done.
In FruitDataGrid, this property should be written as ItemSource="{Binding}" this tells the XAML code to bind to whatever object the DataContext is assigned to in the code behind.
After that, within the DataGrid.Column, this property is needed.
Binding="{Binding Path=.}"
It had to be exactly that for me. lack of the dot or not enclosing it in quotes will not display the fruits.
Thus, for clarity:
In the XAML:
<DataGrid x:Name="FruitDataGrid"
Height="265" VerticalAlignment="Center" Margin="-7,8,-2,-6"
HorizontalAlignment="Left" Width="1188"
AutoGenerateColumns="False"
ItemSource="{Binding}">
<!-- If AutoGenerateColumns was true only the length is displayed.-->
<DataGrid.Columns>
<DataGridTextColumn
x:Name="fruitsDisplay"
Header="Fruits" MinWidth="450"
IsReadOnly="True"
Binding="{Binding Path=.}"/> <!--Exactly like this -->
<DataGridComboBoxColumn
Header="Number of Boxes"
MinWidth ="200"
CanUserResize="True" Width="*" />
</DataGrid.Columns>
And then in the codebehind (filename.xaml.xs)
//Anywhere you plan to bind the list in my case FruitList
List<string> fruitList = new List<string>();
fruitList.add("Melon");
fruitList.add("Mango");
fruitList.add("Banana");
FruitDataGrid.DataContext = fruitList;
And now you'll have a very nice list displayed as a table. What killed 2 days of my time was the binding path should have a . (dot) right there, and I was binding to a string instead of an object (you literally bind to a string "fruitList" when you do Binding = {"fruitList"}. This amateur mistake stems from me insufficiently self-learning XAML.
Thanks again, Muds. I'll select yours as the answer because it helped me, and it feels weird accepting my own answer.
Considering your binding is set to your viewmodel correctly.. do this
ItemsSource="{Binding fruitDataList}"
then
<DataGrid.Columns>
<DataGridTextColumn
x:Name="fruitsDisplay"
Binding="{Binding Path=FRUITS_PROPERTY_NAME_IN_COLLECTION}"
Header="Fruits"
MinWidth="450"
IsReadOnly="True" />
<DataGridComboBoxColumn
ItemsSource="{Binding TO_List_of_Numbers}"
Header="Number of Boxes"
MinWidth ="200"
CanUserResize="True" Width="*"></DataGridComboBoxColumn>

Clear TextBox and update DataGrid from ObvservableCollection

I am using the MVVM pattern to write a WPF application. I created the UI using a TabControl. In one tab I have a list of clients in my company and in the other tab I have a form which is used to add new clients. I want two things:
Adding new client --> new position on the list when the tab with clients is pressed
Clearing form TextBoxes after adding a client
Neither of them works.
My DataGrid part:
<StackPanel DataContext="{StaticResource ClientsVM}">
<Image HorizontalAlignment="Left" VerticalAlignment="Top" Source="pack://application:,,,/Insurance company;component/Resources/logo.png" Height="100" Margin="5,15,0,0"/>
<DataGrid Name="ClientsGrid" ItemsSource="{Binding Clients, Mode=TwoWay}" IsReadOnly="True" Margin="130,0" AutoGenerateColumns="False" ColumnWidth="101">
<DataGrid.Columns>
<DataGridTextColumn Header="Client ID" Binding="{Binding ClientId}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Surname" Binding="{Binding Surname}"/>
<DataGridTextColumn Header="PESEL" Binding="{Binding PESEL}" />
</DataGrid.Columns>
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ClientsGridLeftDoubleClickCommand}" CommandParameter="{Binding ElementName=ClientsGrid, Path=SelectedItem}" />
</DataGrid.InputBindings>
</DataGrid>
</StackPanel>
Part of my ViewModel:
private ObservableCollection<ClientSet> _clients;
public ObservableCollection<ClientSet> Clients
{
get { return _clients; }
set
{
if (_clients != value)
{
_clients = value;
RaisePropertyChanged(() => "Clients");
}
}
}
Unfortunately, doing something like:
_clients = new ObservableCollection<ClientSet>(updatedListOfClientsHere);
doesn't work. Why not?
The other thing is with clearing the form. It looks like this:
<Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="100" FontSize="15">Surname</Label>
<TextBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="141" Name="Surname" Text="{Binding Client.Surname, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="100" FontSize="15">Name</Label>
<TextBox Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="141" Name="Name" Text="{Binding Client.Name, Mode=TwoWay, ValidatesOnDataErrors=True}" />
And the values typed by the user are properly reflected in the ViewModel class. But if I clear them in the ViewModel - nothing happens in the UI. Part of my ViewModel here:
private ClientSet _client;
public ClientSet Client
{
get { return _client; }
set
{
if (value != _client)
{
_client = value;
RaisePropertyChanged(() => "Client");
};
}
}
// Some code
// Clearing the form:
_client = new ClientSet(); // This shouldn't work?
Client.Name = string.empty; // This should work!!!
I am really out of ideas right now.
I have noticed a few problems from your question. First, calling the private member of any property in a view model should only be done when you don't want the UI to update. If you want the UI to update, then you have to use the public properties, because that is what notifies the INotifyPropertyChanged interface (through your RaisePropertyChanged method). So to clear the client list, just call:
Clients = new ObservableCollection<ClientSet>(updatedListOfClientsHere);
Next, I don't know for sure because you didn't show your ClientSet class, but I'm guessing that you didn't implement the INotifyPropertyChanged interface in it. However, you are still expecting property changes of that class to be updated... they won't unless you either implement it in that class, or wrap each property in your view model and call your RaisePropertyChanged method from there. Then, calling Client.Name = string.empty; will clear that field.
I'm not entirely sure what you're asking, but lets see:
First, with your observable collection, you don't need to set it again. If you create a new Client, all you need to do is :
Clients.Add(new_client_object);
That's all. Otherwise, if it takes a while to create the clients, your program will get slower (I've seen this happen, trust me ...)
If I understand correctly, you want to clear the items in the add new client after you add it. All you need to do here is make sure the content control (hopefully your add client view is a usercontrol or something like that) receives an object of type client, and after you add it, just set it to null, or put a new (empty) object, or set them manually to empty strings (Be sure to have a a backup in case you want to cancel, or that you don't overwrite your new client fields).
You could add some screenshots to clarify what you want/need.

BindingContext and a Multiselected datagridview

Getting the current object with BindingContext is no problem but what do I do when I have selected many rows in the datagridview? How do I iterate through each object?
This wasn't easy or fun. Binding multiple selected rows in the datagrid isn't supported by default. I use MultiSelectBehavior from Functional Fun:
http://blog.functionalfun.net/2009/02/how-to-databind-to-selecteditems.html
These are my notes to get it to work:
To get this to work, I did this:
Add this namespace definition to the view:
xmlns:ff="clr-namespace:FunctionalFun.UI.Behaviours;assembly=MultiSelectBehavior"
Within the datagrid, add the last two lines shown here (ff:... and SelectionMode....):
ff:MultiSelectorBehaviours.SynchronizedSelectedItems="{Binding SelectedTasks}"
SelectionMode="Extended"
Note: In the view model, SelectedTasks cannot be null, even when first declared.
No:private ObservableCollection selectedTasks;
Yes: private ObservableCollection selectedTasks = new ObservableCollection();
And this is some actual code that works:
xmlns:ff="clr-namespace:FunctionalFun.UI.Behaviours;assembly=MultiSelectBehavior"
<DataGrid Grid.Row="1" AutoGenerateColumns="False" IsReadOnly="True" HeadersVisibility="Column"
ItemsSource="{Binding SelectedApplicationServer.ApplicationsWithOverrideGroup}"
ff:MultiSelectorBehaviours.SynchronizedSelectedItems="{Binding SelectedApplicationsWithOverrideGroup}"
SelectionMode="Extended">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Application.Name}" Header="Name" Width="150" />
<DataGridTextColumn Binding="{Binding Path=Application.Version}" Header="Version" Width="100"/>
<DataGridTextColumn Binding="{Binding Path=CustomVariableGroup.Name}" Header="Override Group" Width="*"/>
</DataGrid.Columns>
</DataGrid>
Hope it helps.
Edit: I simply added the Functional Fun code as a project within my solution, and then I referenced it within my view project:
Assuming your DataGridView is bound to a BindingSource, using the DataMember property this way :
myDataGridView.DataSource = someBindingSource;
myDataGridView.DataMember = "SomeCollectionProperty";
Then you can retrieve the list of bound items behind your DataGridView:
IList dataBoundItems =
((CurrencyManager)grid.BindingContext[grid.DataSource, grid.DataMember]).List;
You may also want to cast this list to an IEnumerable<T> using :
var myItems = dataBoundItems.OfType<myClass>();

WPF Newbie - How do I store the entire Binding object in a Row of the DataGrid?

I am new to WPF, so there may be an easy answer to this, but it isn't obvious to me.
The grid obviously represents a collection of my Configured Port objects. I want to store the Configured Port object (which is the Binding for each row) in order to pass it to the next UserControl in the MVVM chain when a grid cell is clicked.
I did find this SO question, but the commented section is exactly what I am missing.
Thanks in advance, I'm sure there is an easy solution.
I would use this (everything except for the inner most xaml was taken from the other SO post you linked to:
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Custom Column">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Tag="{Binding}" Content="Click Me" Click="Button_ClickHandler"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridtemplateColumn>
</DataGrid.Columns>
</DataGrid>
Then the source for the click handler:
private void Button_ClickHandler(object sender, EventArgs e)
{
var clickedConfiguredPort = ((FrameworkElement)sender).Tag as ConfiguredPort;
DoStuff(clickedConfiguredPort);
}
Check out this article:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/1b694f75-7621-4c88-8055-6c31c601c87f/
When I did it, I had predefined columns so I used the second solution, but if you are wanting to auto generate the columns, you could implement the first solution.

Categories