How to bind a usercontrol property to Listbox - c#

I am having an issue with using a Listbox in my parent project to display a property defined in a user control. To be more specific, I have created a user control which contains a webbrowsercontrol, and I have defined a property called History which contains the history of urls visited during a browsing session. I also have a parent project, which is linked to this user control, in which I am attempting to bind the History property to a Listbox. The point is for someone to be able to see the history urls in a Listbox defined in this parent project, where the history urls are populated by binding to the user control's History property.
Below is my code outlining what I am trying to do:
User Control FullWebBrowser.xaml.cs
public partial class FullWebBrowser : UserControl
{
//ctr
public FullWebBrowser()
{
InitializeComponent();
//for FullWebBrowser.xaml ContentGrid
ContentGrid.DataContext = this;
}
#region Fields
//The navigation urls of the browser.
private readonly Stack<Uri> _NavigatingUrls = new Stack<Uri>();
//The history for the browser
private readonly ObservableCollection<string> _History = new ObservableCollection<string>();
/// <summary>
/// Gets the History property for the browser.
/// </summary>
public ObservableCollection<string> History
{
get { return _History; }
}
The _NavigatingUrls stack is for the forward and back button implementation, which is working fine, and the _History observablecollection contains the urls from the webbrowsing session shown as follows
//If the navigated uri is not in thehistory, add it
if (!_History.Contains(e.Uri.ToString()))
_History.Add(e.Uri.ToString());
These seem to be working properly, as I have implemented the forward and back buttons and they work ok. The issue is that I cannot properly bind the History property defined in the FullWebBrowser.xaml.cs to my parent project which contains a Listbox. This is shown as follows
HistoryPage.xaml
xmlns:my="clr-namespace:FullBrowserControl;assembly=FullBrowserControl">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Pivot Control-->
<controls:Pivot Title="QUEST">
<!--Pivot item one-->
<controls:PivotItem Header="today">
<Grid/>
</controls:PivotItem>
<!--Pivot item two-->
<controls:PivotItem Header="week">
<Grid/>
</controls:PivotItem>
<!--Pivot item three-->
<controls:PivotItem Header="all">
<StackPanel>
<ScrollViewer>
<ListBox x:Name="ListBox" ItemsSource="{Binding}"
SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=History}" Height="Auto"/>
<TextBlock Foreground="{StaticResource PhoneSubtleBrush}"
Text="{Binding Modified, Converter={StaticResource DateConverter}}"
Margin="24,0,0,12"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Scrollviewer>
</StackPanel>
<!--<Grid/>-->
</controls:PivotItem>
</controls:Pivot>
</Grid>
Note, the dateconverter is ok. Here I am trying to implement a Listbox which shows the url with a timestamp below it.
The code behind for this parent project page is as follows
Historypage.xaml.cs
public partial class HistoryPage : PhoneApplicationPage
{
//Temporary State
public static readonly Setting<int> CurrentHistoryIndex = new Setting<int>("CurrentHistoryIndex", -1);
private FullWebBrowser browser = new FullWebBrowser();
public HistoryPage()
{
InitializeComponent();
//create new instance of FullWebBrowser user control?
this.browser = new FullWebBrowser();
this.DataContext = browser;
//browser.DataContext = this;
//this.DataContext = browser.History;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
//Clear the selection so selecting the same item twice in a row will still raise the SelectedChanged event
CurrentHistoryIndex.Value = -1;
this.ListBox.SelectedIndex = -1;
}
void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListBox.SelectedIndex >= 0)
{
//navigate to the page containing the user control for the selected item
//how to navigate to Mainpage.xaml and load webbrowsercontrol with selected url??
CurrentHistoryIndex.Value = ListBox.SelectedIndex;
this.NavigationService.Navigate(new Uri("/MainPage.xaml", UriKind.Relative));
}
}
}
So this is my basic implementation. No matter what I try I cannot get the Listbox in Historypage to bind to the History property in the FullWebBrowser user control contained outside of the project, I have referenced the FullWebBrowser control using the references option in the solution explorer, in a using declaration at the top of Historypage.xaml.cs, and by an xmlns statement at the top of HistoryPage.xaml
Any assistance with how this may be accomplished would be greatly appreciated! I have been working on this for a couple weeks and cannot find the solution anywhere, even prowling other's posts. I must implement this solution ASAP! thanks for all your help in advance. Please include code to accomplish this, it would help so much to see how this is implemented for future reference!

The DataContext of Your ListBox is FullWebBrowser, therefore your binding should be as follows:
<ListBox x:Name="ListBox" ItemsSource="{Binding Path=History}"/>
To solve this kind of problem yourself try debugging, breakpoint your code then inspect the DataContext properties of the various elements in your UI.

Related

Implement data virtualisation in a UWP ListView without duplicating items

I have a large ListView which is largely made InkCanvas objects, it turns out that ListView implements data virtualisation to "cleverly" unload and load items in the view depending on the visible items in the view. The problem with this is that many times the ListView caches items and when a new item is added it essentially copy items already added in the view. So in my case, if the user adds a stroke to an Inkcanvas and then adds a new InkCanvas to the ListView, the new canvas contains the strokes from the previous canvas. As reported here this is because of the data virtualisation. My ListView is implemented as follows:
<Grid HorizontalAlignment="Stretch">
<ListView x:Name="CanvasListView" IsTapEnabled="False"
IsItemClickEnabled="False"
ScrollViewer.ZoomMode="Enabled"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollMode="Enabled"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Visible"
HorizontalAlignment="Stretch">
<!-- Make sure that items are not clickable and centered-->
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<local:CanvasControl Margin="0 2"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
MinWidth="1000" MinHeight="100" MaxHeight="400"
Background="LightGreen"/>
<Grid HorizontalAlignment="Stretch" Background="Black" Height="2"></Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<InkToolbar x:Name="inkToolbar"
VerticalAlignment="Top"
Background="LightCoral"/>
<StackPanel HorizontalAlignment="Right">
<Button x:Name="AddButton" Content="Add Page" Click="Button_Click"/>
<TextBlock x:Name="PageCountText" />
</StackPanel>
</StackPanel>
</Grid>
A full example can be found here and here is a video of the issue.
Indeed if I turn off data virtualisation (or switch to an ItemsControl) everything works brilliantly. The problem however is that with a very large list, this approach has a heavy impact on performance (with 60+ InkCanvas controls the app just crashes). So is there a way to retain data virtualisation while avoiding the duplication of items? I have tried with VirtualizationMode.Standard but items are still duplicated.
To solve this problem, we must first understand why this problem occurs.
ListView has a reuse container inside, it will not endlessly create new list items, but will recycle.
In most cases, such recycling is not a problem. But it's special for InkCanvas.
InkCanvas is a stateful control. When you draw on InkCanvas, the handwriting is retained and displayed on the UI.
If your control is a TextBlock, this problem does not occur, because we can directly bind the value to TextBlock.Text, but for the Stroke of InkCanvas, we cannot directly bind, which will cause the so-called residue.
So in order to avoid this, we need to clear the state, that is, every time the InkCanvas is created or reloaded, the strokes in the InkCanvas are re-rendered.
1. Create a list for saving stroke information in ViewModel
public class ViewModel : INotifyPropertyChanged
{
// ... other code
public List<InkStroke> Strokes { get; set; }
public ViewModel()
{
Strokes = new List<InkStroke>();
}
}
2. Change the internal structure of CanvasControl
xaml
<Grid>
<InkCanvas x:Name="inkCanvas"
Margin="0 2"
MinWidth="1000"
MinHeight="300"
HorizontalAlignment="Stretch" >
</InkCanvas>
</Grid>
xaml.cs
public sealed partial class CanvasControl : UserControl
{
public CanvasControl()
{
this.InitializeComponent();
// Set supported inking device types.
inkCanvas.InkPresenter.InputDeviceTypes =
Windows.UI.Core.CoreInputDeviceTypes.Mouse |
Windows.UI.Core.CoreInputDeviceTypes.Pen;
}
private void StrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args)
{
if (Data != null)
{
var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes().ToList();
Data.Strokes = strokes.Select(p => p.Clone()).ToList();
}
}
public ViewModel Data
{
get { return (ViewModel)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(ViewModel), typeof(CanvasControl), new PropertyMetadata(null,new PropertyChangedCallback(Data_Changed)));
private static void Data_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(e.NewValue!=null && e.NewValue is ViewModel vm)
{
var strokes = vm.Strokes.Select(p=>p.Clone());
var instance = d as CanvasControl;
instance.inkCanvas.InkPresenter.StrokesCollected -= instance.StrokesCollected;
instance.inkCanvas.InkPresenter.StrokeContainer.Clear();
try
{
instance.inkCanvas.InkPresenter.StrokeContainer.AddStrokes(strokes);
}
catch (Exception)
{
}
instance.inkCanvas.InkPresenter.StrokesCollected += instance.StrokesCollected;
}
}
}
In this way, we can keep our entries stable.

UWP Grid inside Datatemplate

I have a Pivot where I set the header in my Pivot.HeaderTemplate it is basically just showing Names of Books. In my Pivot.ItemTemplate I want to show a Grid which is build in my .xaml.cs but since the Grid is in my DataTemplate I can not access the Grid x:Name anymore in the code behind in .xaml.cs. books is a Collection of Books which contains a Name and a Title
MainPage.xaml
<Pivot ItemsSource="{x:Bind books}">
<Pivot.HeaderTemplate>
<DataTemplate x:DataType="local:Book">
<TextBlock Text="{x:Bind Name}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<Grid
x:Name="BooksGrid"
BorderBrush="Black" BorderThickness="1 1 0 0"
Margin="0 10 0 0>
</Grid>
</DataTemplate>
</Pivot.ItemTemplate>
Now I want to acces BooksGrid iny the code behind and actually create the Grid
MainPage.xaml.cs
public MainPage()
{
this.InitializeComponent();
}
private void DrawGrid()
{
//create columns of Grid
for (int i = 0; i < booksize.XProperties.Count + 1; i++)
{
BooksGrid.ColumnDefinitions.Add(new ColumnDefinition
{
});
}
BooksGrid.ColumnDefinitions[0].Width = GridLength.Auto;
}
....
Already here at BooksGrid.ColumnDefinitions.Add(...) I get the error that BooksGrid can not be found.
My DrawGrid works if I do not place the Grid definition in my DataTemplate and also outside myPivot. So the MainPage.xaml.csdoes not find it when the Grid is inside my DataTemplate
I've read that the solution might be that I have to acces the Grid instance that I want to work with, as soon as the DataTemplate gets loaded. But I do not know how to do that either.
EDIT PART to first solution:
I'm also using BooksGrid in another method
MainPage.xaml.cs
private void DrawBooksFront(Front front)
{
int row;
int column;
column = booksize.XProperties.IndexOf(front.CustomProps[booksize.XLabel])+1;
row = booksize.YProperties.IndexOf(front.CustomProps[booksize.YLabel])+1;
Frame newFrame = new Frame();
TaskBoardGrid.Children.Add(newFrame);
Grid.SetColumn(newFrame, column);
Grid.SetRow(newFrame, row);
}
The reason you cannot access your BooksGrid is because it will be dynamically generated for each book in the books collection. So for every book a Grid will be generated.
OPTION 1:
You can add a Loaded event to your grid:
<Pivot x:Name="Pivot" ItemsSource="{x:Bind books}">
<Pivot.HeaderTemplate>
<DataTemplate x:DataType="local:Book">
<TextBlock Text="{x:Bind Name}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<Grid
BorderBrush="Black" BorderThickness="1,1,0,0"
Margin="0,10,0,0" Loaded="DrawGrid">
</Grid>
</DataTemplate>
</Pivot.ItemTemplate>
and in your code behind:
private void DrawGrid(object sender, RoutedEventArgs e)
{
Grid grid = sender as Grid;
// Load your grid..
}
EDIT - OPTION 2:
If you'd like to access your grids from code behind in a different way (like suggested in your edit) you can always do the following:
private void DrawBooksFront(Front front)
{
// Loop through the pivot's items and get the content from each item's ContentTemplate.
foreach (var item in Pivot.Items)
{
PivotItem pivotItem = Pivot.ContainerFromItem(item) as PivotItem;
Grid grid = pivotItem.ContentTemplate.LoadContent() as Grid;
// Do something with the grid.
}
}
If your goal is to display previews of the pages of the book inside a PivotItem in a grid-like manner [picture below], then you're better off placing GridView in a DataTemplate of Pivot.ItemTemplate and using data binding to display those pages automatically, this would eliminate the need to write the code in xaml.cs that you showed.
Please, share more details about your app (what you're given and what the end result should look like) so we could help you better.

UserControl hidden, Zindex misused?

In my main page I try to show a list of stuff, and on this stuff a userControl as an overlay. In fact I never see it. (In the design view, my userControl is oaky)
XAML MainPage
<Grid>
<Grid x:name="MyPage">
<!-- All this part is visible -->
//Button
//Button
//nice Pic
//Button
</Grid>
<cat:CatPagecontrol x:Name="CatTool" Visibility="Visible" HorizontalAlignment="Center" VerticalAlignment="Center">
<cat:CatPagecontrol.Transitions>
<TransitionCollection>
<PopupThemeTransition/>
</TransitionCollection>
</cat:CatPagecontrol.Transitions>
</cat:CatPagecontrol>
<!-- EDIT I remove the Grid "CatGrid" And the ZIndex -->
</Grid>
I try to switch the ZIndex, no results.
C# File
public MainView()
{
this.CatTool = new CatPagecontrol();
//this.CatTool.Visibility = Visibility.Collapsed;
}
private void showCatSelector()
{
this.CatTool.Visibility = Visibility.Visible;
}
After that I need that one of my buttons show the overlay when clicked.
If you know how to show it, I'm yours. Thanks.
edit : solution find.
Voila !
I've find my problem :
public CatPagecontrol()
{
this.InitializeComponent();
}
I just Initialized in the correct section.

Updating List<object> in codebehind and forcing bindings in XAML to update

I have a method that populates a List with 5 random images. The method returns correctly. When I call the method to populate the List before this.InitializeComponent(); in the codebehind, the images appear on screen. However, when I call the method afterwards, it has no effect on what is shown on screen. What can I do to fix this? It seems like I need to call RaisePropertyChanged() or something along these lines, but I can't find a way to do this. Can anyone please help ?
In my code behind I have the code :
public List<BitmapImage> listOfImages { get; set; }
private async void Get_Images(object sender, RoutedEventArgs e)
{
//code to get 5 random images
IReadOnlyList<StorageFile> fileList = await query.GetFilesAsync();
listOfImages = new List<BitmapImage>();
foreach (StorageFile file in fileList)
{
BitmapImage src = new BitmapImage();
src.SetSource(await file.OpenAsync(FileAccessMode.Read));
listOfRelatedImages.Add(src);
}
}
And in my XAML :
<ItemsControl ItemsSource="{Binding Path=listOfImages}" HorizontalContentAlignment="Stretch">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Center">
<Image x:Name="images" Source="{Binding}" Visibility="Visible" Stretch="Fill">
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
To let the Datagrid know about changes in your List (e.g. add() or remove()) you have to use an ObservableCollection
If you want to inform the DataGrid about changes inside the list (e.g. changing properties) the class shown in the DataGrid has to implement the INotifyPropertyChanged interface
Use ObservableCollection instead of List
ItemsControl track collection changes(add/remove/reset/move) with INotifyCollectionChanged interface, so if you want ItemsControl to update automatically, you need to implement this interface on the list. WPF already contains generic collection that implement this interface.
Replace
List<BitmapImage> listOfImages
With
ObservableCollection<BitmapImage> listOfImages
And it should work

How to get the index of the clicked button in a listbox

I have a list box having delete button in each row so when i clicked the delete button i need the clicked index of list box so as to delete the row.how can i get the index of the clicked item?
here is my listbox
<ListBox HorizontalAlignment="Left" Name="listBox1" Margin="-3,132,0,0" VerticalAlignment="Top" Width="498" SelectionChanged="listBox1_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,1,0,0" BorderBrush="#FFC1BCBC" Width="490">
<Grid Height="70">
<Image Height="50"
HorizontalAlignment="Left"
Name="image"
Stretch="Fill"
VerticalAlignment="Top"
Width="50"
Source="{Binding iconPath}" Margin="8,8,0,0" />
<TextBlock Name="Name" Text="{Binding displayName}" VerticalAlignment="Center" Margin="60,0,0,0" Foreground="Black" FontWeight="SemiBold"></TextBlock>
<Button Name="btnDeleteRow" Width="50" Click="btnDeleteDashboard_Click" Margin="390,0,0,0" BorderBrush="Transparent" Style="{StaticResource logoutbtn_style}">
</Button>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I assume your ListBox is databound to some source collection? If this is the case, the DataContext of your button will be an instance of one of your bound items. You can then do as follows:
// if for example you bind a list of MyDataObject instances ...
// create a list
List<MyDataObject> myDataObjects = CreateTestData();
// bind it
listBox1.ItemSource = myDataObjects;
...
// in your click handler
private void btnDeleteDashboard_Click(object sender, EventArgs args)
{
// cast the sender to a button
Button button = sender as Button;
// find the item that is the datacontext for this button
MyDataObject dataObject = button.DataContent as MyDataObject;
// get the index
int index = myDataObjects.IndexOf(dataObject);
}
A better option is to have the list box databound to a List or ObservableObject collection, then also two way databind the "SelectedItem" or "SelectedIndex" (I prefer selecteditem) to a property.
Then on clicking of the Button you can simply call collection.Remove(selecteditemproperty).
If you are using MVVM or iPropertyNotified then the view will automatically update the list when you change the backend collection.
Let me know if you need a more detailed example for this. but basically:
public ObservableCollection<ItemViewModel> _items;
/// <summary>
/// A collection for ItemViewModel objects.
/// </summary>
public ObservableCollection<ItemViewModel> Items
{
get
{
return _items;
}
set
{
if (value != _items)
{
_items = value;
NotifyPropertyChanged("Items");
}
}
}
private ItemViewModel _listBoxSelectedItem;
/// <summary>
/// Sample ViewModel property; this property is used in the view to display its value using a Binding
/// </summary>
/// <returns></returns>
public ItemViewModel ListBoxSelectedItem
{
get
{
return _listBoxSelectedItem;
}
set
{
if (value != _listBoxSelectedItem)
{
_listBoxSelectedItem = value;
NotifyPropertyChanged("ListBoxSelectedItem");
}
}
}
Then Bind the listbox like this:
ItemsSource="{Binding Items}" SelectedItem="{Binding ListBoxSelectedItem, Mode=TwoWay}"
Then just reference these values as described
Hope this helps
" when i clicked the delete button i need the clicked index", since each row has a delete button, you should assign index to the "Tag" property of each delete button, so whenever you click a delete button, you get the index of correpsponding item of the listbox.
sorry, I just saw your wp tag and your xaml code, so my answer could be wrong.

Categories

Resources