Binding Multiple Download Progress - c#

I have a list of DownloadOperation in ObservableCollection and the variable has the property Progress.TotalBytesToReceive & Progress.BytesReceived.
When i tried to bind this property to max & value of progress bar it gives me binding expression error property does not found. I bind other property ResultFile.Name and it success. Is there any possible way to fix this?
UPDATE :
I found that I need to use converter from progress to get the totalbytes value, but the problem now the value is not updated, and seems like observablecollection does not watch the bytes received value.
<ListView DataContext="{Binding Download}"
ItemsSource="{Binding}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="5">
<TextBlock Text="{Binding ResultFile.Name}"
FontSize="20"
Foreground="Black"/>
<ProgressBar Maximum="100"
Value="{Binding Progress, Converter={StaticResource ByteReceivedConverter}}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have this as viewmodel, the activeDownloads contains list of observable download.
public ObservableCollection<DownloadOperation> activeDownloads = new ObservableCollection<DownloadOperation>();
And I tried to extract the BytesReceived & TotalBytesToReceive in code behind
double received = 0;
double total = 0;
foreach (var item in activeDownloads)
{
received += item.Progress.BytesReceived;
total += item.Progress.TotalBytesToReceive;
}
if (total != 0) {
var percentage = received / total * 100;
It works without any problem, The observablecollection also works fine, when I add download it automatically change the view without I have to update the datacontext manually, same result if the download finish/removed. But if I directly bind the Progress.BytesReceived to the progressbar Value it gives me Path Error, but I am able to bind it to Progress property. So I make a converter to retrieve the value.
Here is Convereter I use to convert the Progress property :
public class ByteReceivedConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
BackgroundDownloadProgress bytes = (BackgroundDownloadProgress)value;
if (bytes.TotalBytesToReceive != 0)
return bytes.BytesReceived/bytes.TotalBytesToReceive*100;
else return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
And is it possible to make this DownloadOperation has observability to the bytesreceived level?, since I did not make this property, I just retrieve it from meta. Or am I doing it wrong? because for now the problem i got is the view does not aware that the bytesreceived value is change.
If it's not possible should I make another viewmodel with INotifyPropertyChanged implemented?

First of all, you don't need to use a converter; binding the Max and Value properties would have been fine, but you probably didn't make the source values as public properties or you had the Path set incorrectly in the binding.
With the way you are doing it now, you will need to raise the property changed event of INotifyPropertyChanged for the Progress property each time BytesReceived is updated, because that is the property that you are binding to.

Related

Bind visibility of text box to view model property in a content template

I am just trying to set a text box to collapsed in my GridViewColumnHeader.ContentTemplate. I believe everything is being done correctly but for some reason the text box won't collapse when the boolean property is set.
Information
I have a View with a View Model that has a FilterRows boolean property.
I am styling my grid view to allow users to set the FilterRows property and have filters for each header appear.
I have tried using a BooleanToVisibilityConverter and a BooleanToCollapsedConverter.
I have verified that the bool is being set in the view model when the user selects a check box.
BooleanToCollapsedConverter
This converter is in the same view as my grid view table. I know the converter works because I have used it on other UI elements in the same view.
<Grid.Resources>
<ResourceDictionary>
<local:BooleanToCollapsedConverter x:Key="BooleanToCollapsedConverter"/>
</ResourceDictionary>
</Grid.Resources>
This is the class for the booleanToCollapsedConverter
public class BooleanToCollapsedConverter : BaseValueConverter<BooleanToCollapsedConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The View
Here is a the code for a part of grid view column where I am trying to use the converter within the style. The GridViewColumnHeader visibility is being set properly and collapsing as needed. The inner portion with the data template is not working though.
<GridViewColumn DisplayMemberBinding = "{Binding Description}"
Width="auto">
<GridViewColumnHeader Content = "Description"
Visibility="{Binding HeaderList[1].IsChecked, Converter={StaticResource BooleanToCollapsedConverter}}"
local:GridViewBehaviors.CollapseableColumn="True">
<GridViewColumnHeader.ContentTemplate>
<DataTemplate>
<StackPanel>
<TextBox Height = "25"
FontSize="{StaticResource FontSizeSmall}"
Visibility="{Binding FilterRows, Converter={StaticResource BooleanToCollapsedConverter}}"/>
<TextBlock Text = "Description" ></ TextBlock >
</ StackPanel >
</ DataTemplate >
</ GridViewColumnHeader.ContentTemplate >
</ GridViewColumnHeader >
</ GridViewColumn >
The View Model
This is my boolean property in the view model.
/// <summary>
/// True if the user wants to apply filters to the rows.
/// Once set the UI will display all filters
/// </summary>
public bool FilterRows
{
get => _filterRows;
set => Set(ref _filterRows, value);
}
Final Points
So overall I am just literally trying to collapse that text box when I check the Filter Rows: check box. Not sure if it has something to do with the style or I am doing something improperly? Any help would be greatly appreciated!
Thanks
try to set data source for textbox binding. How do I use WPF bindings with RelativeSource?
smth like this. typeOfAncestor is UserControl or a Window
{Binding FilterRows, Converter={StaticResource BooleanToCollapsedConverter},
RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
If the view model where the FilterRows property is defined is the DataContext of the parent DataGrid, this should work:
<TextBox Height="25" FontSize="{StaticResource FontSizeSmall}"
Visibility="{Binding DataContext.FilterRows,
RelativeSource={RelativeSource AncestorType=DataGrid},
Converter={StaticResource BooleanToCollapsedConverter}}"/>
Also note that there is a built-in converter for converting between bool and Visibility that you can use instead of creating your own one:
<BooleanToVisibilityConverter x:Key="BooleanToCollapsedConverter" />

Converter not firing after collection update

I have ran into a issue with the converters... They are not triggering once the bound collection is updated although they trigger when the collection is first populated. I would like to have them fire whenever there is a change in the collection.
So far I have built a simple converter:
public class TableConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
VM.Measurement t = ((VM.Measurement)((TextBlock)value).DataContext);
if (t.Delta != null)
{
if (Math.Abs((double)t.Delta) < t.Tol)
return "Green";
else
return "Red";
}
else
return "Red";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
which is linked to a style
<conv:TableConverter x:Key="styleConvStr"/>
<Style x:Key="CellStyleSelectorTol" TargetType="syncfusion:GridCell">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content, Converter={StaticResource styleConvStr}}" />
</Style>
Which is used in this DataGrid
<syncfusion:SfDataGrid x:Name="CheckGrid" BorderBrush="White" Grid.Row="1" Grid.Column="1" AllowEditing="True" ItemsSource="{Binding ChecksList, Mode=TwoWay}" Background="White" SnapsToDevicePixels="False"
ColumnSizer="None" AllowResizingColumns="False" AllowTriStateSorting="True" AllowDraggingColumns="False" CurrentCellEndEdit="CheckGrid_CurrentCellEndEdit" AutoGenerateColumns="False"
NavigationMode="Cell" HeaderRowHeight="30" RowHeight="21" GridPasteOption="None" Margin="20 10 10 10" AllowGrouping="True" SelectedItem="{Binding SelectedLine, Mode=TwoWay}"
SelectionUnit="Row" SelectionMode="Single" RowSelectionBrush="#CBACCB" VirtualizingPanel.IsVirtualizing="True" Visibility="Visible">
<syncfusion:GridTextColumn Width="100" ColumnSizer="SizeToCells" AllowEditing="True" MappingName="Measured" CellStyle="{StaticResource CellStyleSelectorTol}" HeaderText="Measured" TextAlignment="Center" AllowFiltering="False" FilterBehavior="StringTyped"/>
The VM contains an Observable Collection which implements NotifyPropertyChanged all the way down to the Measurement Class. The properties fire up nicely so it is not a binding issue.
private ObservableCollection<Measurement> _checkList = new ObservableCollection<Measurement>();
public ObservableCollection<Measurement> ChecksList
{
get
{
return _checkList;
}
set
{
_checkList = value;
NotifyPropertyChanged();
}
}
Any help with this would be greatly appreciated.
Thanks
EDIT:
Here is the code that updates the collection. Apologies for it being quite messy. Lineitem is the selected line for which Measured and Delta are updated. These are properly displayed in the grid once modified.
public void NewMeasurement(VM.Measurement measurementShell)
{
using (VMEntity DB = new VMEntity())
{
var Check = CheckSets.Where(x => x.ID == SelectedLine.ID).First();
if (Check.Measurement == null)
{
Check.Measurement = measurementShell.Index;
var Lineitem = ChecksList.Where(x => x.ID == SelectedLine.ID).First();
var measurement = DB.Measurements.Where(x => x.Index == Check.Measurement).First();
Lineitem.Measured = (double)measurement.measurement1;
Lineitem.Delta = Lineitem.Measured - Lineitem.Target;
OK, it looks like the problem is that you are changing properties of the cell content item (LineItem, in the NewMeasurement() method), but it's still the same object, so the cell's content doesn't change. The cell's Content is the source for the binding. If that doesn't change, the binding won't wake up and update the target. You're raising PropertyChanged, but this particular binding has no way of knowing you want it to listen to this object for those property changes. Easy enough fix: We'll start telling it exactly what to listen for.
Fortunately the solution means simplifying some of your code. Passing a UI control into a value converter is exotic and not necessary.
What you care about in the converter is Measurement.Delta and Measurement.Tol. When either one changes, the Binding should update its target. You don't want to do that in a clever way. You just want a Binding for each one. That's a Binding's job.
So tell the Binding that you care about those properties, and rewrite the converter to accept both of them as parameters.
<Style x:Key="CellStyleSelectorTol" TargetType="syncfusion:GridCell">
<Setter
Property="Background"
>
<Setter.Value>
<MultiBinding Converter="{StaticResource styleConvStr}">
<Binding Path="Delta" />
<Binding Path="Tol" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
Converter:
public class TableConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// I'm inferring that Measurement.Delta is Nullable<double>; if that's
// not the case, change accordingly. Is it Object instead?
double? delta = (double?)values[0];
double tol = (double)values[1];
if (delta.HasValue && Math.Abs(delta.Value) < tol)
{
return "Green";
}
return "Red";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

WPF create object using IValueConverter with binding object's properties

my objects which I use to binding in XAML can have only string properties. But in binding I need other type. I thought that I use Converter function from IValueConverter, where I'll create object from string properties and return this. One property which is a string will be empty, and in binding I'll return other object from Converter method. I tried this but in Convert method my main object from ObservableCollection is null. This's a piece of my XAML
<Maps:MapItemsControl ItemsSource="{Binding}">
<Maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Background="Transparent" Tapped="ItemStckPanel">
<Image Source="/Assets/pushpin.gif" Height="30" Width="30"
Maps:MapControl.Location="{Binding Location,
Converter={StaticResource StringToGeopoint}}"
Maps:MapControl.NormalizedAnchorPoint="0.5,0.5"/>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Margin="5">
<TextBlock FontSize="20" Foreground="Black" Text="{Binding Name}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</Maps:MapItemsControl.ItemTemplate>
</Maps:MapItemsControl>
And this's my Convert method:
public object Convert(object value, Type targetType, object parameter, string language)
{
Event _event = (Event) parameter;
BasicGeoposition position = new BasicGeoposition();
position.Latitude = _event.Latitude;
position.Longitude = _event.Longitude;
return new Geopoint(position);
}
I want to pass the my actual parent object in Converter method. Solution is change
Maps:MapControl.Location="{Binding Location,
Converter={StaticResource StringToGeopoint}}"
to
Maps:MapControl.Location="{Binding Converter={StaticResource StringToGeopoint}}"
It works :)
The bound object is fed into the "value" parameter of the Convert()-Method.
You're accessing the parameter which corresponds to
<... ConverterParameter= .../>
which isn't set in your xaml.
You would actually have to write your Convert()-Method like this:
public object Convert(object value, Type targetType, object parameter, string language)
{
Event _event = (Event) value;
BasicGeoposition position = new BasicGeoposition();
position.Latitude = _event.Latitude;
position.Longitude = _event.Longitude;
return new Geopoint(position);
}
/UPDATE:
The ItemsSource={Binding} on your Maps:MapItemControl binds to the DataContext of the parent object. This should be your ObservableCollection.
Within the ItemTemplate your Image has a "Location"-Property that is bound to the "Location"-property of each item within your ObservableCollection. You could also write:
{Binding Path=Location, Converter={StaticResource StringToGeopoint}}
Now before that binding is fully evaluated, the Object that is stored in the Location-property is passed to the converter and the result is then handed to the "Location"-Property on the Image.
If you are getting null objects to be passed to the "value"-parameter, that means that the original Binding hands null values to the Converter either because the Property on the source object is null or because the property doesn't exist.

Doing the maths of numbers in a listbox based on the first character of each item

I have a program that adds items to a list box. The items consist of a character (+,-,*,/) followed by numbers. I want to get my program to recognize the first character of the item in the list box, and do the math appropriately. However, I cannot seem to get it right. I'm not even sure what code I should be using.
Just to make it a little more clear
Items get entered into a textbox called txtCalculation using this format "+34233". That value is then added into the list box lstDisplay so after 5 items it should look like this.
+2
+4
/2
-1
+5
*3
total = 21
I would like to find a way to make the program go through the list and do the math, and then add it as a final item to the list as shown above. I already have the code to add the numbers to the list and an attempt to add them together and then put them in a textbox called txtTest, but everytime I add a new number txtTest is just set to the last number I added to the listbox instead of displaying the overall total of these numbers
Code that I have so far
double total = 0;
string line = "";
if (txtCalculation.Text.Length > 1)
{
if (e.KeyChar == (char)Keys.Enter)
{
string Number = txtCalculation.Text;
try
{
switch (Number[0])
{
case '+':
total += Convert.ToInt32(Number.Substring(1));
break;
case '-':
total -= Convert.ToInt32(Number.Substring(1));
break;
}
lstDisplay.Items.Add(Number);
txtCalculation.Text = "";
txtTest.Text = total.ToString();
}
catch (Exception ex)
{
MessageBox.Show("Error");
}
}
I would really appreciate if someone could help me fix this code up and explain to me what I am doing wrong.
I've made a small example on this:
<Window x:Class="AddTotalListConverter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AddTotalListConverter"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<local:ComputationConverter x:Key="ComputationConverter"/>
</Grid.Resources>
<StackPanel>
<ListView x:Name="lvNumbers" ItemsSource="{Binding numbers}">
<ListView.View>
<GridView>
<GridViewColumn Header="Numbers">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<StackPanel Orientation="Horizontal" Margin="0,10">
<TextBlock Text="New Number" Margin="5,0"/>
<TextBox x:Name="tbNewNumber" MinWidth="50" Margin="5,0"/>
<Button Content="Add Number" Margin="5,0" Click="btnAddNumber_Click"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,20"/>
<TextBlock Text="{Binding numbers.Count, Converter={StaticResource ComputationConverter}, ConverterParameter={x:Reference lvNumbers}}"/>
</StackPanel>
</Grid>
This is the View. Nothing too fancy, just the ListView bounded to the ObservableCollection numbers. The TextBlock which will display the total is bounded to the items count. So, every time you add or remove an item, it will be updated. A converter is present there and its parameter is the ListView itself.
class ComputationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int result = 0;
ListView lv = parameter as ListView;
Console.WriteLine(lv.Items);
foreach (var item in lv.Items)
{
result += (int)item;
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Again, i kept it simple without string parsings just to finish it on time, coffee time, sorry.
Indeed i work with ints, you will receive string, parse the sign and take the value also in order to continue with your case switches flow.
Here is the code behing:
public partial class MainWindow : Window
{
public ObservableCollection<int> numbers { get; set; }
public MainWindow()
{
InitializeComponent();
numbers = new ObservableCollection<int>();
this.DataContext = this;
}
private void btnAddNumber_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(tbNewNumber.Text))
{
numbers.Add(int.Parse(tbNewNumber.Text));
}
}
}
If you need an MVVM approach i could make an update later, using commands and having the list in the ViewModel.
I hope this helps.
I hate to assume, but the code you provided above appears to be code inside of a method which is not provided as part of your code snip. There are two options to fix your problem, if my assumptions are correct. The first option would be to move your "total" variable out of the method and give it a class scope. By declaring the variable "total" inside your method, you are clearing it to 0 every time your method is called. Giving it a class scope should keep the value of the variable every time you call the method/event. The second option would be set the value of your "total" variable to txtTest.Text. You would have to convert txtTest.Text to a double. There are a few different ways to convert a string to a double. You can research the following methods: double.Parse(string input), double.TryParse(string input, out double output), or Convert.ToDouble(string input) to do the conversion.

Adding IValueConverter breaks updates when changed occur from observable collection to UI

I have a itemscontrol with the item template set to a border, then i bind the datacontext of the listview to a list of objects that contain a bool property.
Then i added a click event handler to the border, and when detecting a click, i cast the datacontext of the border to the class and set the bool field to true.
That works as a charm, but i want the rectangle to have a specific colour when the bool field is set to true or false, so i created a IValueConverter that takes my class and returns a colour.
That works too, the rectangles are different colors based on the bool field.
I am still able to click the rectangles, but they just arent updated.
The color of the rectangle wont change.
Datatemplate from the itemscontrol itemtemplate
<DataTemplate>
<Border ToolTip="{Binding Seat.Column}" Width="25" Height="25" Margin="0,0,2,2" BorderBrush="Black" Background="{Binding Converter={StaticResource ResourceKey=SeatStateConverter}}" BorderThickness="2" Name="rectangle1" VerticalAlignment="Center" HorizontalAlignment="Center" MouseLeftButtonDown="rectangle1_MouseLeftButtonDown">
<Label Content="{Binding Occupied}" Foreground="White" FontSize="7"></Label>
</Border>
</DataTemplate>
The click event
private void rectangle1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Border item = sender as Border;
SeatState state = item.DataContext as SeatState;
state.Locked = !state.Locked;
}
my converter
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SeatState state = value as SeatState;
if (state == null)
return null;
SolidColorBrush brush = new SolidColorBrush();
if (state.Occupied)
{
brush.Color = Color.FromRgb(172, 0,0);
}
else if (state.Locked)
{
brush.Color = Color.FromRgb(214, 65, 0);
}
else if(!state.Occupied)
{
brush.Color = Color.FromRgb(0, 172, 0);
}
return brush;
}
This works great.. untill i add the converter that converts the objects into a SolidColorBrush.
I tried all sorts of crazy stuff that should have nothing to do with my problem.
implementing the convertback method
in the IValueConverter interface
setting the binding to a two way binding
invoking the UpdateLayout
method on the ItemsControl
But nothing seemed to work.
Anyone got any ideas?
My english could be better so please ask if there is anything you want clearified =)
Thanks in advance.
I think you are binding to the SeatState object - whereas you actually need to bind to some combination of the Occupied an Locked properties?
i.e. it's not the SeatState object itself that is changing, but rather its a couple of the properties of the SeatState.
Maybe merge the properties together somehow and set this merged property as the Path for the XAML Background.
e.g. within SeatState
private bool _Locked
public bool Locked
{
get
{
return _Locked;
}
set
{
_Locked = value;
NotifyPropertyChange("Locked");
NotifyPropertyChange("LockedAndOccupied");
}
}
private bool _Occupied
public bool Occupied
{
get
{
return _Occupied;
}
set
{
_Occupied = value;
NotifyPropertyChange("Occupied");
NotifyPropertyChange("LockedAndOccupied");
}
}
public Tuple<bool, bool> LockedAndOccupied
{
get
{
return new Tuple<bool, bool>(Locked, Occupied);
}
}
then in the XAML you can bind to Path=LockedAndOccupied, Converter=...
Obviously you'll have to change the Converter code too - I'll let you do that!
Alternatively... now I've read up about it...
There is something called a MultiBinding - http://www.switchonthecode.com/tutorials/wpf-tutorial-using-multibindings - looks perfect for your needs
Something like:
<Border.Background>
<MultiBinding Converter="{StaticResource aNewConverter}">
<Binding Path="Locked"/>
<Binding Path="Occupied"/>
</MultiBinding>
</Border.Background>
I've learnt something new tonight :)
Check the Background binding... it looks like your Path is missing. I would expect to see something like...
Background="{Binding Path=., Converter={StaticResource ResourceKey=SeatStateConverter}}"
Alternately you could try setting BindsDirectlyToSource=true.
On second thought, you probably need to implement an IMultiValueConverter, and then bind each of the properties separately. That may be what you need to do to get the change notifications on each of the properties. Here is an example of an IMultiValueConverter implementation from MSDN.
Also, you may want to check your implementation of INotifyPropertyChanged... misspellings of property names will break the change notifications...

Categories

Resources