I have a window defined in XAML that has a bing map element. Without copying the whole window, the relevant part of the XAML is
<m:Map x:Name="myMap" CredentialsProvider="BLABLA" Mode="Road">
<m:MapItemsControl ItemsSource="{Binding Devices, Mode=TwoWay}">
<m:MapItemsControl.ItemTemplate>
<DataTemplate>
<m:MapPolyline Locations="{Binding Locations, Mode=TwoWay}"
Fill="Green" Stroke="Blue"
StrokeThickness="4" Opacity="1"/>
</DataTemplate>
</m:MapItemsControl.ItemTemplate>
</m:MapItemsControl>
</m:Map>
Devices is an ObservableCollection<PushPinSet>, where PushPinSet is defined has follows
public class PushPinSet
{
public DateTime LastSeen { get; set; }
public LocationCollection Locations { get; set; }
}
When I create the ObservableCollection Devices (almost) everything is fine: the MapPolylines are drawn accordingly to the LocationCollection. I say almost, because for some unknown reason it always skips the first item. I work around it by adding a fake item at the beginning of the collection.
When I try to create a new LocationCollection in one of the items of devices in order to deform the shape, the corresponding MapPolyline is not updated.
So I have two questions:
Do you see any bug in my XAML that justifies the fact that the first item is not displayed?
Why are MapPolylines not updated? I am thinking that for some reason when I add/remove items to Devices I emit correctly the PropertyChanged event, but the same does not happen when I edit the specific item. If this is correct what and how should I signal, so that the UI works as expected?
UPDATE
By following the suggestion of mm8 I implemented INotifyPropertyChanged and it works a little bit more: the shape does not change, but i can see it change if I zoom out and in or I drag the map. Can I automatically force a redraw of the shapes? I also removed Mode=TwoWay, since it was not necessary.
I checked with a bunch of writelines, the collection's count is always n+1, where n is the number of items I want to add and the +1 is due to the fake item.
Related
I am having a hard time figuring what's going on with my animation.
Viewmodel is composed of an ObservableCollection and every item contains a child ObservableCollection.
Parents collection is bound to a BindableLayout. The ItemTemplate of that layout contains a Listview to display child's elements.
<StackLayout BindableLayout.ItemsSource ="{Binding Parents}">
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="models:ParentRows">
<StackLayout>
<Grid BackgroundColor="White" >
<!-- Some bindable content there -->
</Grid>
<ListView ItemsSource="{Binding Childs}" CachingStrategy="RecycleElementAndDataTemplate" RowHeight="50">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:ChildRows">
<ViewCell>
<StackLayout BackgroundColor="White">
<controls:AnimatedGrid Refresh="{Binding Animation}"
<!-- Some bindable content there -->
</controls:AnimatedGrid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
I am using an AnimatedGrid on the child listview, this control is inherinting from Grid. It has an extra BindableProperty nammed Refresh and an Animation code that gets called whenever Refresh property changes.
private async void AnimateItem()
{
await Device.InvokeOnMainThreadAsync(() =>
{
this.RotateTo(360,500);
});
}
Everything works fine until i start filtering the list. Once i filter the list, subsequent call of AnimateItem will have no effect.
To be more precise, if parent item got removed from list, and then added again, childs of this parent will never animate again.
Filtering the List consist of Removing/Inserting parents to the observable collection (myCollection.Remove(item), myCollection.Insert(index, item), using Collection methods from framework).
This does not seems to be an observable collection binding issue, as values inside parent and childs collection still update perfectly find.
Changing CachingStrategy also have no impact on the issue.
I found that, if i replace the ListView control by a CollectionView, the problem disappear. However, i realy want to find a solution that would alow me to keep the listview control as switching to CollectionView would introduce to many other undesirable effect.
Edit 22/02/2022 :
I made a sample project to reproduce the issue on github.
Basicaly, you can click the "Rotate Random" multiple time to make
random child spin.
Once you click the "Remove and add 2 parent", you
can see that the removed/reinserted items does not rotate anymore.
Edit 15/03/2022 :
I am still not able to figure what's wrong.
However, for test purpose, i added in the control constructor, a task.delay followed by an animation call, and this call is working on filtered items. That's beyond my understanding.
The solution
In your AnimatedGrid class, add an isAttached flag, and add the following lines to your OnPropertyChanged override:
bool isAttached = false;
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "Renderer")
{
isAttached = !isAttached;
if (!isAttached) this.RemoveBinding(RefreshProperty);
}
if (propertyName == nameof(Refresh))
{
...
The why
Disclaimer: it took me a long time to figure out what was going on here, and although i was able to fix the problem, i would not claim that i completely and perfectly understand it. I believe this is a bug in Xamarin, and in your case i would file an issue in Github (although maybe MAUI will have this corrected...)
When a ChildItem is removed (do Filter), the old ChildItem-AnimatedGrid's Refresh property remains bound to the ChildItem's AnimationInt property.
When a ChildItem is added again (remove Filter), a new view is created.
Now the problem is evident: when the ChildItem's AnimationInt property changes (tap on ROTATE RANDOM button) the old ChildItem-AnimatedGrid's Refresh is notified, and then the old View is rotated, but the new ramains unchanged (it does not rotate).
Once the problem is understood, we need to figure out how to remove the binding of the old view when the view is dettached: well, to do this i used the fact that the VisualElement's Renderer property is set/modified when the element is attached and again when it is detached: The fist time it is called i set the isAttached flag to true. The second time it is called i set the flag to false, and i remove the binding. Removing the binding of the old View allows the new View to be correctly bound.
Let me state problem first. I would like to implement wrapper around Canvas (let me call it Page) which would implement selecting rectangle around its UIElements which are actually selected.
For this I implemented ISelect interface like so :
interface ISelect {
Point Center {get; set;} //Center of selecting rectangle
Size Dimensions {get; set;} //Dimensions of selecting rectangle
}
Every object that is put to Page implements ISelect interface.
Page has SelectedElements of type ObservableCollection which holds reference to all currently selected elements.
For every entry in SelectedElements i would like to draw rectangle around it.
I have few ideas how to do this :
Every UIElement can implement on its own this rectangle and show it when selected. This option would require for new objects to implement this every time. So I rather not use it.
In Page I could create rectangles in code-behind in add them to the Page. It isn't MVVM recommended priniciple.
In Page XAML create somehind like ItemsControl and bind it to SelectedElements with specific template. This option seems like the best one to me. Please help me in this direction. Should I somehow use ItemsControl?
Thank you.
I don't have time to dig a complete working solution, so this is mostly a collection of suggestions.
Each element should have view model
public abstract class Element: INotifyPropertyChanged
{
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged();
}
}
}
public class EllipseElement : Element {}
public class RectangleElement : Element {}
Then there are data templates to visualize elements (I can't give you converter code, but you can replace it with another, look here).
<DataTemplate DataType="{x:Type local:EllipseElement}">
<Border Visibility="{Binding IsSelected, Converter={local:FalseToHiddenConverter}}">
<Ellipse ... />
</Border>
</DataTemplate>
<DataTemplate DataType="{x:Type local:RectangleElement}">
<Border Visibility="{Binding IsSelected, Converter={local:FalseToHiddenConverter}}">
<Rectangle ... />
</Border>
</DataTemplate>
Then bind ObservableCollection of elements to canvas (which is tricky, see this answer, where ItemsControl is used to support binding).
Your selection routine has to hit-test elements and set/reset their IsSelected property, which will show border. See here regarding how to draw over-all selection rectangle.
I have a WPF GUI setup like this currently:
The "Check for Third Party Updates" button will query the machine for outdated application installs and display the results, each update grouped in its own row/section with some text describing the update and a button allowing them to initiate the install.
I have a class built for third party updates that contains application name, version, installpath, message to display, etc. My question is largely how to implement the visual components. Every time the list of "apps to be updated" is iterated through and a member is found, a new row needs to be generated with common elements (button, text, picture,etc.). And I don't know how many rows might be generated, so I need to allow for the potential of scrolling down within the tab. Is a listbox control the way to go? How can I setup a visual template for the rows that are dynamically created to adhere to?
A ListBox would be a sensible approach. You would have to create a DataTemplate for the ListBoxItems and assign that to the ItemTemplate property of the ListBox, as described in Styling and Templating an ItemsControl. All the rest, like the ability to select items, or to scroll through the list, is of course done automatically by the ListBox control.
It might look like this:
<ListBox ItemsSource="{Binding ThirdPartyUpdates}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding AppIcon}" Margin="5"/>
<TextBlock Text="{Binding AppName}" Margin="5"/>
<TextBlock Text="{Binding AppVersion}" Margin="5"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The view model for the above ListBox would be something like this:
public class ThirdPartyUpdate
{
public string AppIcon { get; set; }
public string AppName { get; set; }
public string AppVersion { get; set; }
}
public class ViewModel
{
public ObservableCollection<ThirdPartyUpdate> ThirdPartyUpdates { get; set; }
}
You can use ItemsControl and bind it to a collection of your Class and use ItemsControl's template to bind your data to whichever control you want. Check out this Example
For each item in the collection you will have a row created. Surround the item control with a ScrollViewer. Set the VerticalScrollbar visibility to auto so that it will be visible only when required. And if you set a maximumheight to a value you feel right and set the height to auto. It will grow till the maximum height and the scroll bar will be visible if items are added beyond that.
I'm starting out with C# Windows Phone 8 development and I am trying to write an app which uses a LongListSelector. The app will show a long list of train station names.
I have been looking at some of the samples online, including the MSDN PeopleHub, and PhotoHub samples but they seem very complex. I took the PeopleHub sample and hacked at it a bit until I got it to display a list of stations in a LongListSelector but I wasn't able to find out which item had been selected in the list. The SelectedItem property was just returning the app name when passed to another page and not which item had been picked in the list.
I think I need a basic example of how to add some items to a LongListSelector and then find and pass the selected item to another page. I don't fully understand how the control works, if you have to use some sort of DataBinding with the LongListSelector to populate it or whether it's something simpler like:
LongListSelectorThing.add("trainstationA");
LongListSelectorThing.add("trainstationB");
LongListSelectorThing.add("trainstationC");
etc
Can someone give me some simple basic pointers as to how to populate this control and find out which item the user selects? When I say which item they select, when the LongListSelector grid appears, they click on A for example, and it then shows a list of things beginning with A and they then click on trainstationA, I'd want to be able to detect they've picked trainstationA and pass that info to another page so I can display further information about it.
Sorry for if this seems basic, I'm quite new to this.
Thanks!
Here is a basic example which should help you understand:
First in your Page (xaml file) you define the control LongListSelector (LLS):
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<phone:LongListSelector Name="myLLS" Margin="0">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
You also declare how its Items will look like. It can be any UIElement - a button, Image, Grid and so on. In the code above I declared that my Item would be a TextBlock which content (text) I've bound to a property 'Name'. I've also given the LLS a name, that I can refer to it later.
In Page.cs code you populate the LLS. Lets create the simple Station class, and populate LLS:
public class Station
{
private string _stationName;
public string Name
{
get { return _stationName; }
set { _stationName = value; }
}
public Station(string station)
{
this.Name = station;
}
}
public partial class MainPage : PhoneApplicationPage
{
ObservableCollection<Station> trainStations = new ObservableCollection<Station>();
public MainPage()
{
InitializeComponent();
myLLS.ItemsSource = trainStations;
trainStations.Add(new Station("Germany"));
trainStations.Add(new Station("France"));
trainStations.Add(new Station("Italy"));
}
}
What is important:
look that in my Station class there is property called 'Name' - that's the one to which content of the TextBlock is bound.
I've created ObservableCollection which is a collection of my Stations - it's similar to a List but when the new item is added or removed the PropertyChanged event is raised and therefore your LongListSelector can be automatically updated when you add a new station.
I've assigned created collection to myLLS.ItemsSource - it means that created LLS will be populated with Items (described in xaml as DataTemplate) and a Source of this items is that collection.
Hope this helps. Happy coding.
I've got a WPF DataGrid that I have been using for some time, and it works great. Unlike other posters here, I haven't had issues with the scrollbar or mouse wheel (yet). I have CTRLEND programmed to go to the end of the DataGrid, and then it tracks the most-recently added items. I can scroll up through the DataGrid contents with the up key.
However, I have really weird behavior with the down key! If I start from the top of my DataGrid and hold the down key, it scrolls for a little bit and then eventually bounces back and forth between two adjacent rows. If I pgdn, it will scroll down more, then jump back to the topmost of the previous two rows that it would jump between, then scroll down to the point that I pgdn'd to. If I page down some more, the down key will scroll to the end. If I go to the top of the DataGrid and start over, I get the exact same behavior, over and over again.
I've yet to find a post that addresses this, and I haven't seen anything in the DataGrid documentation that helps.
It's just a three-column DataGrid, where each column displays TextBlocks. Can anyone explain why just this one mode of scrolling is problematic? Here's the XAML:
<DataGrid ItemsSource="{Binding MainLog}" AutoGenerateColumns="False"
Name="log_datagrid" SelectedCellsChanged="log_datagrid_SelectedCellsChanged"
KeyUp="datagrid_KeyUp" LoadingRow="log_datagrid_LoadingRow">
<DataGrid.Columns>
<!-- timestamp -->
<DataGridTemplateColumn Header="Timestamp">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Timestamp}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- level -->
<DataGridTemplateColumn Header="Level">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Level}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- error message -->
<DataGridTemplateColumn Header="Message">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Message}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
By the way, this behavior occurs even with all of my code-behind for the event handlers commented out.
Here is the definition of the struct that my MainLog collection contains:
public struct MainLogData
{
public string Timestamp { get; set; }
public string Level { get; set; }
public string Message { get; set; }
}
Ok... I reproduced the behavior with strings (simple list of strings bound to the data grid). The behavior started happening when I introduced duplicates strings in my list. It seems that the data grid gets confused between the "selected index" and the "selected value".
The same kind of thing happens when I try to select a value (a string, in my test) that is present on another visible row: the selection gets screwed up: half of the time, the correct row is not selected.
Your problem is that you are using a "struct". The simple solution to your problem is to make your struct a class:
public class MainLogData
{
public string Timestamp { get; set; }
public string Level { get; set; }
public string Message { get; set; }
}
Just changing the struct word to class should fix your problem.
You must understand that structs and classes are not the same, and structs determine their "equality" to another variable (with the same type) based on the values in them (2 variables of a specific structure type containing the same data will be considered equal). In the case of classes, unless specified otherwise, the equality is determined by its memory address; this ensures that by default no 2 instances of an object, even if they contain identical data, will not be considered equal because they do no reside at the same memory address (this behavior can be overwritten by overwriting "GetHashCode" and "Equals" methods in any class definition).
So in conclusion, the DataGrid has problems determining which item your are selecting (or moving too with your arrow key) because many objects in your list are considered "the same", or "equal". That is why it gets confused. Admittedly, I think this is a datagrid bug (or at least stranger behavior if it is by design), but changing your data type from a struct to a class should help you get back on track!
Cheers