Binding to Listbox programmatically in silverlight - c#

I want a listbox that will show all the images and text "layers" that I have on my Canvas in silverlight. The code I have currently crashes when I try to view the listbox or when I'm viewing the listbox when I add an element. I can't figure out why. Can someone point me in the right direction with this?
XML -
<Grid DataContext="{Binding Path=Project}">
...
...
<TextBlock Name="textBlock1" Text="Layers" Margin="18,16,0,0" />
<StackPanel Grid.Row="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
<ListBox ItemsSource="{Binding Path=Elements}" Height="175" Name="listBox1" Width="172"/>
</StackPanel>
</Grid>
Project.cs
//List of elements
private ObservableCollection<FrameworkElement> elements;
public ObservableCollection<FrameworkElement> Elements
{
get { return elements; }
set
{
elements = value;
NotifyPropertyChanged("Elements");
}
}
// An example of how an element is added to the Elements collection
// There are also image elements added similarly
private void AddTextElement(object param)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = "New Text";
textBlock.Foreground = new SolidColorBrush(Colors.Gray);
textBlock.FontSize = 25;
textBlock.FontFamily = new FontFamily("Arial");
textBlock.Cursor = Cursors.Hand;
textBlock.Tag = null;
this.Elements.Add(textBlock);
numberOfElements++;
this.SelectedElement = textBlock;
this.selectedTextElement = textBlock;
}
private void AddImageElement(object param)
{
bool? gotImage;
string fileName;
BitmapImage imageSource = GetImageFromLocalMachine(out gotImage, out fileName);
if (gotImage == true)
{
Image image = new Image();
OrderElements(image);
image.Name = fileName;
image.Source = imageSource;
image.Height = imageSource.PixelHeight;
image.Width = imageSource.PixelWidth;
image.MaxHeight = imageSource.PixelHeight;
image.MaxWidth = imageSource.PixelWidth;
image.Cursor = Cursors.Hand;
image.Tag = null;
AddDraggingBehavior(image);
image.MouseLeftButtonUp += element_MouseLeftButtonUp;
this.Elements.Add(image);
numberOfElements++;
this.SelectedElement = image;
this.SelectedImageElement = image;
}
}

One reason might be, because you bind using Path property in your Grid element.
You should use binding source, and set your Project object as a staticresource which you can point to when you call binding source.
Like this:
<Window
xlmns:local="NamespaceOfMyProject">
<Window.Resources>
<local:Project x:key="MyProjectResource" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource MyProjectResource}}>
....
</Grid>
....
</Window>
Reason is: You use "Source" when you point to objects, and "Path" when you point to properties.
Another way to set the DataContext is to do it in the codebehind, using this C# code. But first give your grid a name, so it can be referenced in the codebehind:
<Grid x:Name="myGrid">
Codebehind:
myGrid.DataContext = new Project();

Working with Images incorrectly will typically cause the crash; Show the code for implementation of your Elements and how you setting the images.
Also your XAML is missing ItemTemplate,where u would set the image and text.

I'd guess it's crashing because you've got FrameworkElements that you've added to a Canvas, but then you're also adding them to your List. FrameworkElements generally don't like being added to the visual tree multiple times.
If this is the problem, something like this might work around it (bind your list to ElementsAsStrings):
private ObservableCollection<FrameworkElement> elements;
public ObservableCollection<FrameworkElement> Elements
{
get { return elements; }
set
{
if(elements != null)
elements.CollectionChanged -= onElementsChanged;
elements = value;
if(elements != null)
elements.CollectionChanged += onElementsChanged;
NotifyPropertyChanged("Elements");
NotifyPropertyChanged("ElementsAsStrings");
}
}
public IEnumerable<string> ElementsAsStrings
{
get
{
foreach(var element in Elements)
{
if(element is TextBox)
yield return (element as TextBox).Text;
// More cases here
}
}
}
private void onElementsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("ElementsAsStrings");
}

Related

ListBox filled with binding doesn't select item on click

I'm trying to use a ListBox to choose an entry and then display a picture belonging to this selected entry. But just at the beginning I got my first problem: filling the ListBox with binding is working, but if I click on one line in my running program, it doesn't select the line. I can just see the highlighted hover effect, but not select a line. Any ideas what my mistake could be?
This is my XAML:
<ListBox x:Name="entrySelection" ItemsSource="{Binding Path=entryItems}" HorizontalAlignment="Left" Height="335" Margin="428,349,0,0" VerticalAlignment="Top" Width="540" FontSize="24"/>
And in MainWindow.xaml.cs I'm filling the ListBox with entries:
private void fillEntrySelectionListBox()
{
//Fill listBox with entries for active user
DataContext = this;
entryItems = new ObservableCollection<ComboBoxItem>();
foreach (HistoryEntry h in activeUser.History)
{
var cbItem = new ComboBoxItem();
cbItem.Content = h.toString();
entryItems.Add(cbItem);
}
this.entrySelection.ItemsSource = entryItems;
labelEntrySelection.Text = "Einträge für: " + activeUser.Id;
//show image matching the selected entry
if (activeUser.History != null)
{
int index = entrySelection.SelectedIndex;
if (index != -1 && index < activeUser.History.Count)
{
this.entryImage.Source = activeUser.History[index].Image;
}
}
}
So I can see my ListBox correctly filled, but not select anything - so I can't go on with loading the picture matching the selected entry.
I'm still quite new to programming, so any help would be great :)
EDIT: If someone takes a look at this thread later: here's the - quite obvious -solution
XAML now looks like this
<ListBox x:Name="entrySelection" ItemsSource="{Binding Path=entryItems}" HorizontalAlignment="Left" Height="335" Margin="428,349,0,0" VerticalAlignment="Top" Width="540" FontFamily="Siemens sans" FontSize="24">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind to fill it:
//Fill listbox with entries for selected user
DataContext = this;
entryItems = new ObservableCollection<DataItem>();
foreach (HistoryEntry h in selectedUser.History)
{
var lbItem = new DataItem(h.toString());
entryItems.Add(lbItem);
}
this.entrySelection.ItemsSource = entryItems;
labelEntrySelection.Text = "Einträge für: " + selectedUser.Id;
And new Class DataItem:
class DataItem
{
private String text;
public DataItem(String s)
{
text = s;
}
public String Text
{
get
{
return text;
}
}
}
You are filling it with ComboBoxItem, which is not relevant to the ListBox, and also wrong by definition.
You need to have the ObservableCollection filled with data items.
Meaning, make a class that contains the data you want to store, and the ListBox will generate a ListBoxItem automatically per data item.
http://www.wpf-tutorial.com/list-controls/listbox-control/

Clone Grid Windows Phone 8

I have a Grid template in XAML CODE:
<Grid x:name="gridTemplate>
Childrens...
</Grid>
Now I want to place this grid in LongListSelector in foreach loop:
foreach(var item in myList)
{
clonedGrid= ??? (need clone here my xaml control)
longlistselector.Items.Add(clonedGrid):
}
This works for me for WPF:
public static class ExtensionMethods
{
public static T XamlClone<T>(this T original)
where T : class
{
if (original == null)
return null;
object clone;
using (var stream = new MemoryStream())
{
XamlWriter.Save(original, stream);
stream.Seek(0, SeekOrigin.Begin);
clone = XamlReader.Load(stream);
}
if (clone is T)
return (T)clone;
else
return null;
}
}
How to implement this in WINDOWS PHONE 8?
I would make a contentControl, where I could put dataTemplate from resources like this:
xaml
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="MyGrid">
<Grid>
<!-- here is your data template, where you can bind to item's properties -->
</Grid>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
cs
foreach (var item in myList)
{
ContentControl control = new ContentControl();
control.Content = item;
control.ContentTemplate = Resources["MyGrid"] as DataTemplate;
longlistselector.Items.Add(control);
}
If you Grid does not have many properties set, then just create method that creates new Grid and pass it as argument your old Grid so you can set all the properties to be same.
public Grid CloneGrid(Grid input)
{
Grid temp = new Grid();
temp.Width = input.Widht;
... etc
return temp;
}
EDIT:
Another way is to define your properties as Style in App.xaml and then apply it to the Grid:
Grid.Style = App.Current.Resources[StyleKey] as Style;

How to create multiple buttons from existing strings in .txt file

I wonder how I can create buttons in my Toolbar by reading lines from a .txt file.
For example:
//bookmarks.txt
http://example.com
http://example2.com
http://example3.com
...
What I want is that my program on start should create a button for each line in my .txt with this event:
public void Button_Click(object sender, RoutedEventArgs e) //fire bookmark event
{
string text = e.Source.ToString().Replace("System.Windows.Controls.Button: ", "");
WebBrowser1.Navigate(text);
}
UPDATE
This is how I read the .txt:
for (int i = 0; i < File.ReadLines(#"bookmarks.txt").Count(); i++)
{
//Add button right here
}
You're trying to use WPF as if it were WinForms. This is how you would fulfil your requirements in WPF... first create a DependencyProperty collection in your Window code behind and populate it with your text entries:
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<string>), typeof(YourWindowOrUserControl));
public ObservableCollection<string> Items
{
get { return (ObservableCollection<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
...
Items = new ObservableCollection<string>(File.ReadLines(#"bookmarks.txt"));
Then you simply data bind the collection to the ToolBar.ItemsSource property and declare a DataTemplate to define what each string should look like... in your case, we'll set it as the text in a Button:
<ToolBar ItemsSource="{Binding Items}">
<ToolBar.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" Margin="1,0,0,0" />
</DataTemplate>
</ToolBar.ItemTemplate>
</ToolBar>
Of course, you'll need to set the Window.DataContext to the class with your properties... the simplest way is to set it in the code behind constructor like this:
public YourWindowOrUserControl
{
InitializeComponent();
DataContext = this;
}
You must read up about how to set the DataContext properly though, as setting it this way is easy, but not necessarily correct.
Finally, you could create a class with all the necessary properties for the Button... for example, you could add a property named Text and another called Command and then make your Items property a collection of those. Then you could data bind to it like this:
<ToolBar ItemsSource="{Binding Items}">
<ToolBar.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Text}" Command="{Binding Command}" Margin="1,0,0,0" />
</DataTemplate>
</ToolBar.ItemTemplate>
</ToolBar>
You can create buttons dynamic and add click event on fly:
Button btn = new Button();
btn.Location = new Point(yourX, yourY);
btn.Font = new Font(btn.Font.Name, 10);
btn.Text = "Text from your txt file here";
btn.ForeColor = Color.SeaShell; // choose color
btn.AutoSize = true;
btn.Click += (sender, eventArgs) =>
{
string text = btn.Text.Replace("System.Windows.Controls.Button: ", "");
WebBrowser1.Navigate(text);
};
(Insert this code in your For. Btw, you can replace the for with while. see this link)

Move behavior inside ItemsControl

I've got a Collection of items bound to an ItemsControl element.
I need to enable the mouse dragging behavior for each element but I can't seem to make it work.
Adding this code
<i:Interaction.Behaviors>
<el:MouseDragElementBehavior/>
</i:Interaction.Behaviors>
will make it work but only if I put my items outside the ItemsControl.
Here's the code of the ItemsControl component:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<utils:TemplateSelector Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Am I doing something wrong?
EDIT: New code but still not working (with and without the Canvas)
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<utils:TemplateSelector Content="{Binding}">
<i:Interaction.Behaviors>
<el:MouseDragElementBehavior/>
</i:Interaction.Behaviors>
</utils:TemplateSelector>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Seems like that once you start the bounty you suddenly find a solution which you were not able to find in the past months.
I'm answering to add my solution, that currently works but surely can be done better.
What I'm proposing is based on two different sources:
A gesture-driven Windows Phone to-do application by Colin Eberhardt
How to find element in visual tree? wp7 answered by E.Z. Hart
Let's start with the XAML:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="{StaticResource PhoneForegroundBrush}">
<utils:TemplateSelector Content="{Binding}"/>
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
DragStarted="GestureListener_OnDragStarted"
DragDelta="GestureListener_OnDragDelta"
DragCompleted="GestureListener_OnDragCompleted"/>
</toolkit:GestureService.GestureListener>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="WidgetsCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
(toolkit comes from Windows Phone Toolkit)
Now let's see the implementation of the three drag handlers.
private int _zindex; //Used to keep the moved element on the top, not working for now
private FrameworkElement _movedObject; //The element that we're moving. Used to avoid moving multiple items if they overlap
private void GestureListener_OnDragStarted(object sender, DragStartedGestureEventArgs e)
{
if (_movedObject != null) return; // We're already moving something!
// Initialize the drag
var fe = sender as FrameworkElement; // The element that we want to move
(fe as Border).BorderThickness = new Thickness(5); // A simple effect to mark the element on the screen
_movedObject = fe; // We set the current object to the one which is moving
Canvas.SetZIndex(fe, _zindex++); // This should take the moved object on the top but it's not working
}
private void GestureListener_OnDragDelta(object sender, DragDeltaGestureEventArgs e)
{
var fe = sender as FrameworkElement;
if (!fe.Equals(_movedObject)) return; // We change the object's position only if this is the one who started the event
var offset = DragManager.GetOffset(fe); // We get the current position
var canvas = DragManager.FindChild<Canvas>(Application.Current.RootVisual, "ItemsCanvas"); // We need the container of our object to force it to stay inside the container
//The new position is given by the old one plus the change reported by the event
var horizontalOffset = offset.HorizontalValue + e.HorizontalChange;
var verticalOffset = offset.VerticalValue + e.VerticalChange;
// We need to check if the new position is outside our container's bounds
if (horizontalOffset < 0) horizontalOffset = 0;
else if (horizontalOffset > (canvas.ActualWidth - fe.ActualWidth)) horizontalOffset = canvas.ActualWidth - fe.ActualWidth;
if (verticalOffset < 0) verticalOffset = 0;
else if (verticalOffset > (canvas.ActualHeight - fe.ActualHeight)) verticalOffset = canvas.ActualHeight - fe.ActualHeight;
// Once we've got everything set, we can move our component
DragManager.SetOffset(fe, horizontalOffset, verticalOffset);
}
private void GestureListener_OnDragCompleted(object sender, DragCompletedGestureEventArgs e)
{
var fe = sender as FrameworkElement;
(fe as Border).BorderThickness = new Thickness(0); // We undo our effect
_movedObject = null; // The movement is done so we can reset our current object and wait for a new one to come
}
the handlers work with a class that I called DragManager. Its code it's quite simplet:
public static class DragManager
{
public static void SetOffset(FrameworkElement fe, double horizontalOffset, double verticalOffset)
{
var trans = new TranslateTransform
{
X = horizontalOffset,
Y = verticalOffset
};
// I don't know what may change, in terms of performance, between applying the transform or just changing the margins. I'm using the margins because the transform may be needed for some other purpose
//fe.RenderTransform = trans;
fe.Margin = new Thickness(horizontalOffset, verticalOffset, 0, 0); // We just change our object's margins to reflect its new position
// We store the current position in the objects Tag (maybe there's a better solution but I'm quite new to C#/xaml)
fe.Tag = new Offset
{
VerticalValue = verticalOffset,
HorizontalValue = horizontalOffset,
Transform = trans
};
}
public static Offset GetOffset(FrameworkElement fe)
{
if (fe.Tag == null) fe.Tag = new Offset();
return (Offset)fe.Tag;
}
public struct Offset
{
public double HorizontalValue { get; set; }
public double VerticalValue { get; set; }
public TranslateTransform Transform { get; set; }
}
public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null)
{
return null;
}
T foundChild = null;
var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
var childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null)
{
break;
}
}
else if (!String.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
// Need this in case the element we want is nested
// in another element of the same type
foundChild = FindChild<T>(child, childName);
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
}
As I already said, this code is working right now.
If anybody has some suggestion to improve it, please write it here so that I can see/test a better way to do this kind of stuff!

Want Images as PanoramaItems in WP7?

I have a Panorama control where i need to programmaticaly add items which are images.
I want to implement them so that flicking on the image slide to second image and so on..
I did add images to the panaroma item but it always shows one image only.
for (int i = 0; i < 10; i++)
{
image_new = new Image();
PanoramaItem pi = new PanoramaItem();
image_new.Source = "Some image Bitmap";
pi.Content = image_new;
image_panaroma.Items.Add(pi);
}
xaml layout is :
<Grid x:Name="LayoutRoot">
<controls:Panorama Name="image_panaroma">
</controls:Panorama>
</Grid>
Can someone tell me what is wrong?
Also is there any other way possible to give sliding transition to images?
You don't say what exception you get, but I think a better approach would be to create an ItemTemplate for the Panorama control and bind it to your list of objects.
public class ItemData
{
public string Name { get; set; }
public string Path { get; set; }
}
...
List<ItemData> items = new List<ItemData>(10);
for (int i = 0; i < 10; i++)
{
items.Add(new ItemData { Name = "Something", Path = "Image path" });
}
this.image_panorama.ItemsSource = items;
Your XAML would look something like this:
<controls:Panorama x:Name="image_panorama">
<controls:Panorama.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</controls:Panorama.HeaderTemplate>
<controls:Panorama.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Path}" />
</DataTemplate>
</controls:Panorama.ItemTemplate>
</controls:Panorama>
You say that this only shows 1 image. Is this the same image 10 times (one for each pivotItem) or is only 1 pivotItem being created?
If it's the first then it could be how you're creating the image and/or setting the source.
Your example code doesn't show that you're using a different image source or how image_new is scoped. Without a more complete example of your actual code it's hard to say for sure.
The following will (works-on-my-machine) create 10 items all with the same image:
public MainPage()
{
InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
for (var i = 0; i < 10; i++)
{
var image_new = new Image();
var pi = new PanoramaItem();
var bi = new BitmapImage(new Uri("/Background.png", UriKind.Relative));
image_new.Source = bi;
pi.Content = image_new;
image_panaroma.Items.Add(pi);
}
}
Are you using the January Update? (I am-see above for WOMM disclaimer) This update includes changes that affect the panorama control.

Categories