Refreshing OxyPlot graphs doesn't work - c#

I have a Xamarin forms application which has an event that runs every second and fetches data from a Characteristic. In this event I create the whole layout, set the data and update the view like this:
public void UpdateDisplay (ICharacteristic c) {
//Lots of irrelevant code above this.
foreach (Accelerometer item in acceloReadings)
{
counter += 1;
//Y-graph data
DataPoint itemPointY = new DataPoint();
itemPointY.X = counter;
itemPointY.Y = item.yAxes;
PointsY.Add(itemPointY);
}
yAs = new PlotModel("Variations Y-Axis");
yAs.PlotType = PlotType.XY;
yAs.Axes.Add(new LinearAxis(AxisPosition.Left, -270, 270));
y = new LineSeries();
//Custom color
y.Color = OxyColor.FromArgb(255, 154, 200, 253);
y.ItemsSource = PointsY;
yAs.Series.Add(y);
opvY = new OxyPlotView
{
WidthRequest = 100,
HeightRequest = 100,
BackgroundColor = Color.Aqua
};
opvY.Model = yAs;
Content = new StackLayout
{
opvY,
}
};
}
That code fills up all the models etc and I then want to update my graphs in the same method. I've tried a few ways:
//After all the data is re-loaded and added I want to refresh my graphs.
xAs.InvalidatePlot(true);
opvX.Model.InvalidatePlot(true);
opvX.InvalidateDisplay();
However none of these have any succes. When you look at the official docs you can see clearly it states:
"To update the plot, you must do one of the following alternatives:
Change the Model property of the PlotView control
Call Invalidate on the PlotView control
Call Invalidate on the PlotModel"
While I've tried all these ways none of them update my graphs/view. When I manually click and resize the graph I can see the data is refreshed. Eventually I want to create the graphs just once in the main method and then update the graphs with the InvalidateDisplay.
So, what am I doing wrong?!

Related

Xamarin.Forms CollectionView View Recycling Issues

So I am using a CollectionView to display a list of entities that, when pressed, toggle between a opened and closed state. I did this by animating the HeightRequest parameter of the parent container of the view that was pressed, then adding whatever views I wanted to show in the view's expanded state. Here is the code snippet for that:
var animate = new Animation(d => this.HeightRequest = d,
this.Bounds.Height, this.Bounds.Height + 300, Easing.CubicOut);
animate.Commit(this, "a", length: 500);
this.layout.Children.Add(this.candidateList);
this.layout.Children.Add(this.openButton);
This works fine, however, if I scroll down the list, I see that there are views that are also expanded even though I did not touch them previously, more so every full page of scrolling later. Some even include the views that I've added to the expanded state, showing incorrect data. I have assumed that this is due to the recycling mechanics of the CollectionView work in order to save on rendering costs, but there must be some way to fix this. Here is the code for relevant views:
var officesList = new CollectionView
{
ItemTemplate = new DataTemplate(typeof(OfficeListView)),
HorizontalOptions = LayoutOptions.FillAndExpand,
ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical)
{
SnapPointsType = SnapPointsType.None,
ItemSpacing = 10,
},
Margin = new Thickness(20, 5),
ItemSizingStrategy = ItemSizingStrategy.MeasureAllItems,
Footer = " ",
FooterTemplate = new DataTemplate(() =>
{
return new StackLayout
{
Margin = new Thickness(20, 10),
Children = {
new LocorumLabels.MediumLabel
{
Text = "x Results | No filters applied" //TODO: Bind these to footer
}
}
};
})
};
The next snippet is the CollectionsView within an expanded "Office":
this.candidateList = new CollectionView
{
ItemTemplate = new DataTemplate(typeof(CandidateDetailView)),
HorizontalOptions = LayoutOptions.FillAndExpand,
ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical)
{
SnapPointsType = SnapPointsType.None,
//ItemSpacing = 10,
},
Margin = new Thickness(10, 5),
ItemSizingStrategy = ItemSizingStrategy.MeasureAllItems,
Footer = " ",
HeightRequest = 300
};
And here is a video showing what is going on:
https://youtu.be/Ltg2o8BwfwY
Hopefully someone can let me know of a solution. Many thanks.
You are right on the cause.
As CollectionView uses DataTemplate you need to set your views and your data in such a way that when recycled the view appears as it should.
The non-obvious part to keep the animation working is to call the animation as of now but when it completes to set the value in data that would alter the view from the state before the animation to the state after the animation.

Xamarin / C# : How to iterate through Array/List DataMember passed to the viewCell without using inner ListView

I've been advised that Xamarin doesn't support nested ListViews.. I trying to build social app, in which the post might have several images and comments.
The feed page will be basically a ListView of the posts. While I cannot use a listView within the post (which is a viewCell), I tried to use Grid for the images instead, and another Grid for Comments. Both Images and Comments can be Lists, Arrays or ObservableCollections in the "post" Class.
Now I need to make foreach loop of this array/list, adding images with source bound to array item. But it seems to me, I couldn't use the data passed to the viewCell in C# (can only be used in Xaml layout).
Anybody has any ideas how to solve this.
[DataContract]
Public class post {
[DataMember]
List<comment> commentContainer {get; set;}
[DataMember]
List<String> imageContainer {get; set;}
}
in Xaml of cellView
<Grid x:Name="imagesGrid" IsVisible="{Binding isImage}" BindingContext="{Binding imageContainer}">
here I need to iterate the list of image in c# if possible - runtime- !
</Grid>
You are right to say that you shouldn't use nested ListViews in Xamarin.Forms, virtualization goes out the window and scrolling/gestures will be hard to handle.
However, i think this design in Xamarin.Forms will be fraught with problems. Xamarin.Forms ListView is a very specific beast and wont play well with very complicated layouts. My gut feeling is you want to do this in Native Views maybe or at the very least design this entirely in code. Anyway that is just my gut feel.
Have it be noted, i would shy away from this design and make it simple for your self by doing like WhatsApp does, and use a Template Selector and break apart multiple images and posts with a DataTemplateSelecto. Then you Don't have to worry about complicated layouts.
However, if you have your heart set on more complicated layouts inside ListView (be warned there be dragons) though you can take a look at this, Cell Appearance As noted i would be more inclined to this entirely in Code apposed to an ad-hock Xaml solution
public class CustomCell : ViewCell
{
public CustomCell()
{
//instantiate each of our views
var image = new Image ();
StackLayout cellWrapper = new StackLayout ();
StackLayout horizontalLayout = new StackLayout ();
Label left = new Label ();
Label right = new Label ();
...
//add views to the view hierarchy
horizontalLayout.Children.Add (image);
horizontalLayout.Children.Add (left);
horizontalLayout.Children.Add (right);
cellWrapper.Children.Add (horizontalLayout);
View = cellWrapper;
}
}
And set it like this
public partial class ImageCellPage : ContentPage
{
public ImageCellPage ()
{
InitializeComponent ();
listView.ItemTemplate = new DataTemplate (typeof(CustomCell));
}
}
Also checkout Grid
You can build one in code like this
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star)});
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star)});
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star)});
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star)});
var topLeft = new Label { Text = "Top Left" };
var topRight = new Label { Text = "Top Right" };
var bottomLeft = new Label { Text = "Bottom Left" };
var bottomRight = new Label { Text = "Bottom Right" };
grid.Children.Add(topLeft, 0, 0);
grid.Children.Add(topRight, 0, 1);
grid.Children.Add(bottomLeft, 1, 0);
grid.Children.Add(bottomRight, 1, 1);

Create Multiple Labels in Data Template

I have created a data template to use within a list view. This will later be expanded to add more content to each item in this list view. At the moment all the items that are bound to the observable collection are working as expected, except for one.
In each instance of the data template the bound properties are height, RouteName and routeStops. The height and RouteName are working fine but I'm not sure how to bind the routeStops correctly.
For each one of the RouteNames there are multiple stops, so for each data template use there must be one label that has the RouteName and multiple labels for each stop on the route (using routeStops).
I am not entirely sure how to achieve this, I can only seem to bind one stop to one label. I want to create them dynamically to allow for any amount of stops.
So the code behind that creates the data template (Just the constructor):
public MainRoutePageViewDetail(MessagDatabase database)
{
InitializeComponent();
BindingContext = mainroutepageviewmodel = new MainRoutePageViewModel(database,Navigation);
StackLayout mainstack = new StackLayout();
var routelisttemplate = new DataTemplate(() => {
ViewCell viewcell = new ViewCell();
stacklayout = new StackLayout();
stacklayout.SetBinding(StackLayout.HeightRequestProperty,"height");
viewcell.View = stacklayout;
// labels for template
var nameLabel = new Label { FontAttributes = FontAttributes.Bold, BackgroundColor = Color.LightGray };
nameLabel.HorizontalOptions = LayoutOptions.Center;
nameLabel.VerticalOptions = LayoutOptions.Center;
nameLabel.SetBinding(Label.TextProperty, "RouteName");
//inforLabel.SetBinding(Label.TextProperty, "Stops");
stacklayout.Children.Add(nameLabel);
StackLayout nextstack = new StackLayout();
var nameLabel2 = new Label { FontAttributes = FontAttributes.Bold, BackgroundColor = Color.Red };
nameLabel2.HorizontalOptions = LayoutOptions.Center;
nameLabel2.VerticalOptions = LayoutOptions.Center;
nameLabel2.SetBinding(Label.TextProperty, "routeStops");
nextstack.Children.Add(nameLabel2);
stacklayout.Children.Add(nextstack);
return viewcell;
});
ListView listviewofroutes = new ListView();
mainstack.Children.Add(listviewofroutes);
listviewofroutes.SetBinding(ListView.ItemsSourceProperty, "routeLabels");
listviewofroutes.ItemTemplate = routelisttemplate;
listviewofroutes.HasUnevenRows = true;
Content = mainstack;
}// end of constructor
This is bound to an ObservableCollection in the view model. Im going to leave this out as its irrelevant because the bindings work fine.
This calls down to functions in the model that collect data from SQL tables.
The function in the model that collects data:
public List<RouteInfo> getrouteInfo()
{
var DataBaseSelection = _connection.Query<RouteInfoTable>("Select * From [RouteInfoTable]");
List<RouteInfo> dataList = new List<RouteInfo>();
for (var i = 0; i < DataBaseSelection.Count; i++)
{
var DataBaseSelection2 = _connection.Query<RouteStopsTable>("Select StopOnRoute From [RouteStopsTable] WHERE RouteName = ? ",DataBaseSelection[i].RouteName);
dataList.Add(new RouteInfo
{
ID = DataBaseSelection[i].ID,
RouteName = DataBaseSelection[i].RouteName,
Stops = DataBaseSelection[i].Stops,
DayOf = DataBaseSelection[i].DayOf,
IsVisible = DataBaseSelection[i].IsVisible,
routeStops = DataBaseSelection2[i].StopOnRoute,
height = 200
});
}
return dataList;
}
The first table (RouteInfoTable) gets RouteName and some other information and the second table gets the stops on the route using the RouteName as a key. This is all added to a list of RouteInfo instances.
DataBaseSelection2 grabs all of the stops on the route but only one of them displays. I know why this is but I dont know how to display all three.
The Table definitions and class definitions as well as the selections from the tables are not an issue. I have debugged these and they are getting the correct information I just dont know how to display it on the front end in the way I want to. Here is a visual of what I mean if its getting complicated:
The best I can do is one route stop not all three.
An ideas how to achieve this?
Sorry if its complicated.
You can use Grouping in ListView to achieve this visual. Basically in this case you will be defining two DataTemplate(s) -
GroupHeaderTemplate for RouteName
ItemTemplate for StopsOnRoute.

MonoTouch Working with Views and Controllers

I am trying to create a screen in my MonoTouch C# app that lets users search for restaurants. The user can search for restaurants nearby, anywhere, or recently visited. These three options are presented as a segmented control. When a user clicks an item in the segmented control, I toggle the visible views. In other words each option represents its own view.
I want each view to have its own table view controller such that when a user clicks a restaurant, more details are given to the user. From what I can tell, I need to implement the approach shown here: http://www.alexyork.net/blog/post/UINavigationController-with-MonoTouch-Building-a-simple-RSS-reader-Part-1.aspx
My problem is, I can't seem to figure out a way to add a controller to a view. In my scenario, I believe I need to add a UITableViewController to each UIView (one for each segmented control item). Is this possible? If so, how? If it is not possible, how should I go about accomplishing my goal? This seems simple enough, but I seem to be way off and am misunderstanding something. Here is the method I'm calling when the ViewDidLoad event is hit:
private void LoadViews()
{
int subViewHeight = 320;
#region Nearby View
RectangleF nearbyRectangle = new RectangleF(0, 0, UIScreen.MainScreen.Bounds.Width, subViewHeight);
this.nearbyView = new UIView(nearbyRectangle);
this.nearbyView.BackgroundColor = UIColor.Blue;
this.nearbyTableViewController = new NearbyTableViewController(IntPtr.Zero);
this.NavigationController.PushViewController(nearbyTableViewController, false);
this.View.Add(nearbyView);
#endregion Nearby View
#region Elsewhere View
RectangleF elsewhereRectangle = new RectangleF(0, 0, UIScreen.MainScreen.Bounds.Width, subViewHeight);
this.elsewhereView = new UIView(elsewhereRectangle);
this.elsewhereView.Hidden = true;
this.elsewhereView.BackgroundColor = UIColor.Green;
// Add the search text field
UITextField searchElsewhereTextField = new UITextField();
searchElsewhereTextField.BorderStyle = UITextBorderStyle.RoundedRect;
searchElsewhereTextField.Frame = new RectangleF(20, 13, 200, 31);
searchElsewhereTextField.Placeholder = "query";
this.elsewhereView.AddSubview(searchElsewhereTextField);
// Add the search button
UIButton searchButton = UIButton.FromType(UIButtonType.RoundedRect);
searchButton.Frame = new RectangleF((UIScreen.MainScreen.Bounds.Width - 90), 13, 70, 31);
searchButton.SetTitle("Search", UIControlState.Normal);
this.elsewhereView.AddSubview(searchButton);
// Add the results table
this.elsewhereTableView = new UITableView(new RectangleF(0, 52,
UIScreen.MainScreen.Bounds.Width, subViewHeight-70), UITableViewStyle.Plain);
this.elsewhereTableView.Source = new NearbyListDataSource(this);
this.elsewhereView.AddSubview(elsewhereTableView);
this.View.Add(elsewhereView);
#endregion Elsewhere View
#region Recent View
RectangleF recentRectangle = new RectangleF(0, 0, UIScreen.MainScreen.Bounds.Width, subViewHeight);
this.recentView = new UIView(recentRectangle);
this.recentView.Hidden = true;
this.recentTableView = new UITableView(new RectangleF(0, 0, UIScreen.MainScreen.Bounds.Width, subViewHeight), UITableViewStyle.Plain);
this.recentTableView.Source = new NearbyListDataSource(this);
this.recentView.AddSubview(recentTableView);
this.View.Add(recentView);
#endregion Recent View
}
Thank you for your help!
Usually if you want to transition between UIViewControllers you would use UINavigationController and "push" them into the screen. In this case you want a UISegmentedControl inside the UINavigationController's UINavigationBar.
this.PushViewController(myRecentRestaurantsViewController, true);
When the associated item is clicked in the segmented control, you call the method above to push the appropriate controller into view.
Basically UIViewControllers are "containers" for views. If you ever want to add a ViewController's View to another ViewController's view please look at my blog post on custom containers:
http://blog.devnos.com/wont-somebody-please-think-of-the-children

LayoutGrid SetRow and SetColumn not working as intended

I have code that creates a button for each object in a list. When each object is created it is given a name that corresponds to the row and column (i.e. name = row1col2). Each button is generated dynamically, added to the grid and then the row/column are set. At a later time I need to gather the "selected" button data so I can perform actions on the data they represent. When I attempt to get the control data from the buttons everything is fine, except for the grid row/column data. It remains the same for all of the selected rows and columns in a grid.
Creating buttons:
for (int i = 1; i < row.StackCount+1; i++)
{
//create button for the column
stackButton = new Button();
stackButton.Height = ((newRow.Height - 2));
stackButton.Width = ((newRow.Width / row.StackCount) - 2);
stackButton.Background = new SolidColorBrush(Colors.White);
//add the button border
stackButton.BorderBrush = new SolidColorBrush(Colors.Black);
stackButton.BorderThickness = new Thickness(1);
stackButton.Style = Application.Current.Resources["flatButton"] as Style;
//add the button name
stackButton.Name = "Row" + row.LineNumber + "Col" + (i - 1).ToString();
//add the event handler to the button
stackButton.Click += new RoutedEventHandler(stackButton_Click);
//add a new column
newRow.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(newRow.Width, GridUnitType.Star) });
//put the button into the grid
newRow.Children.Add(stackButton);
Grid.SetRow(stackButton, 0);
Grid.SetColumn(stackButton, i-1);
}
Getting the Button data back
g = (Grid)b.Child;
foreach (Button currentButton in g.Children)
{
if (((SolidColorBrush)currentButton.Background).Color == Colors.Gray)
{
//create a stack object
buttonData.StartDate = DateTime.Now;
buttonData.LotNumber = LotDisplay.Text;
buttonData.RoomID = SilverGlobals.CurrentRoom.RoomID;
buttonData.RoomCol = Grid.GetColumn(currentButton);
buttonData.RoomRow = Grid.GetRow(currentButton);
buttonData.TrayCount = int.Parse(currentButton.Content.ToString());
buttonData.Status = 0;
//add stack object to list of stack objects
stacks.Add(buttonData);
}
}
I know this must be something small I am missing. Anyone got any ideas?
Although the comment in your second section of code says:
//create a stack object
you don't actually create a new stack object so it is simply overwriting the single instance of buttonData on each iteration. The values for row and column that you see at the end are the last iteration's values.
The net effect is that stacks is a collection of all the same instance of an object instead of a collection of separate instances.
This is just a shot in the dark, but based on this question and this question, it might be that you need to set the Row and Column properties before you add the button to its parent - something like this:
//put the button into the grid
Grid.SetRow(stackButton, 0);
Grid.SetColumn(stackButton, i-1);
newRow.Children.Add(stackButton);

Categories