WPF container for Image template - wpf

I'm storing the urls to the images in a sql ce 3.5 database as strings. I want to retrieve the urls and display them in the main application window. Here is the code:
DataSet myDataSet;
private void OnInit(object sender, EventArgs e)
{
string connString = Properties.Settings.Default.SystemicsAnalystDBConnectionString;
OleDbConnection conn = new OleDbConnection(connString);
OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT url FROM Library;", conn);
myDataSet = new DataSet();
adapter.Fill(myDataSet, "Library");
myListBox.DataContext = myDataSet;
}
The first problem is that I don't think the method onInit is fired. But I don't know the reason for that.
The second problem is with XAML file. I need a container for images (like the listbox for textboxes) and since I won't know how many images are there I need some kind of a template:
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=url}" />
</StackPanel>
</DataTemplate>
But there has to be some kind of a container that would have the datacontext set to the data source.
Could anyone help?

You can customize a listbox in wpf quite easily to have images in it, instead of text. Use the ItemTemplate or if you want to change to control itself, the ControlTemplate.

<ListBox ItemsSource="{Binding Library}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Path=url}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The DataContext for the ListBox should be your DataSet. You can use OnLoad instead of OnInit
Anyway I dont recommend the DataSet binding, it would be more managable if you create ViewModel class for your Library and create a collection of Library entities

Related

Modifying XAML-based DataTemplate in DataTemplateSelector

Is there a way to modify DataTemplate before returning it in DataTemplateSelector?
My DataTemplate is defined in XAML. There is an element in this template that I need to set binding for, but whose binding path will only be decided at run-time. The template looks like this:
<DataTemplate DataType="vm:FormField">
<StackPanel>
<ComboBox ItemsSource="{Binding ValueList.DefaultView}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Mode=OneWay}" /> <!--This is the problem child-->
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
TextBlock.Text needs to set its binding path to a property that will be supplied by the underlying data item. My DataTemplateSelector uses the following code to assign it the new path:
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
//MultiValueTemplate is the above template
var Content = MultiValueTemplate.LoadContent() as StackPanel;
var ComboItemText = (Content.Children[0] as ComboBox).ItemTemplate.LoadContent() as TextBlock;
//Underlying item contains the field name that I want this to bind to.
ComboItemText.SetBinding(TextBlock.TextProperty, (item as MyItemVM).FieldName);
return MultiValueTemplate;
}
This doesn't work. Code runs, but the output doesn't set TextProperty binding. What do I need to change/add?
Note: I have solved this problem using FrameworkElementFactory approach, but I have had to redefine the entire DataTemplate in the code (which is a pain even for simple template like the one above). I want to use the one that I have already defined in XAML.
Note 2: FrameworkElementFactory approach assigns the constructed template object to DataTemplate.VisualTree in the last step, just before returning. I think it is that part that I'm missing, but there is no way of doing that since VisualTree asks for an object of FrameworkElementFactory type, which we do not have when using XAML-based template.
Background
We are basically getting JSON structure from the server-side that looks something like this:
`[
"Person":
{
"Name": "Peter",
"Score": 53000
},
"Person":
{
"Name": "dotNET",
"Score": 24000
}
,...
]
What fields will be included in JSON will be determined by the server. Our application is required to parse this JSON and then display as many ComboBoxes as there are fields. Each ComboBox will then list down one field in it. So in the above example, there will be one combo for Names and one for Scores. User can choose an option either from the first or second ComboBox, but selecting from one combo will automatically select corresponding item from the other combo(s).
Now you may ask, who the hell designed this idiotic UI? Unfortunately we neither know nor control this decision. I ask the client to instead use ONE Combo (instead of many) with a DataGrid as its dropdown, so that we could display one data item per grid row and user could choose one of those items. Clear and Simple. But the management didn't agree and here we are trying to mimic synchronized comboboxes. LOL.
So what we're currently doing is to transform incoming JSON to a DataTable on-the-fly. This DataTable gets one column for each JSON field and as many row as their are items; kind of pivoting you can say. We then create ComboBoes and bind each one to a single field of this DataTable. This field name is of course dynamic and is decided at run-time, which mean that I have to modify the DataTemplate at run-time, which brings up this question.
Hope it didn't get too boring! :)
look like you can bind SelectedValuePath and DisplayMemberPath to FieldName and be done with that:
<ComboBox SelectedValuePath="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.FieldName}"
DisplayMemberPath="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.FieldName}"/>
Note: For future readers, as mentioned by #ASh in his answer, DisplayMemberPath is a DependencyProperty and can be used to bind to a dynamic field name. This solution in this answer would be over-engineering for this particular problem. I'll still keep it here as it can be useful in certain other scenarios where Binding might not be enough.
Figured it out and was easier than I thought. Instead of modifying the template in DataTemplateSelector, I'm now using a Behavior to modify binding path at runtime. Here is the Behavior:
public class DynamicBindingPathBehavior : Behavior<TextBlock>
{
public string BindingPath
{
get { return (string)GetValue(BindingPathProperty); }
set { SetValue(BindingPathProperty, value); }
}
public static readonly DependencyProperty BindingPathProperty =
DependencyProperty.Register("BindingPath", typeof(string), typeof(DynamicBindingPathBehavior),
new FrameworkPropertyMetadata(null, (sender, e) =>
{
var Behavior = (sender as DynamicBindingPathBehavior);
Behavior.AssociatedObject.SetBinding(TextBlock.TextProperty, new Binding(Behavior.BindingPath));
}));
}
And here is the modification that I had to make in my XAML template:
<DataTemplate DataType="vm:FormField">
<StackPanel>
<ComboBox ItemsSource="{Binding ValueList.DefaultView}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Mode=OneWay}">
<e:Interaction.Behaviors>
<local:DynamicBindingPathBehavior BindingPath="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.FieldName, Mode=OneWay}" />
</e:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
All works well from this point forward.
The other approach is to create your template programmatically in your DataTemplateSelector. If you want to go down that route, here is a rough sketch of how to do it in SelectTemplate function:
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var DT = new DataTemplate();
FrameworkElementFactory stackpanelElement = new FrameworkElementFactory(typeof(StackPanel), "stackpanel");
FrameworkElementFactory comboboxElement = new FrameworkElementFactory(typeof(ComboBox), "combobox");
comboboxElement.SetBinding(ComboBox.ItemsSourceProperty, new Binding() { Path = new PropertyPath("ValueList.DefaultView") });
comboboxElement.SetBinding(ComboBox.SelectedItemProperty, new Binding() { Path = new PropertyPath("Value") });
var ItemTemplate = new DataTemplate();
FrameworkElementFactory textblockElement2 = new FrameworkElementFactory(typeof(TextBlock), "textblock2");
textblockElement2.SetBinding(TextBlock.TextProperty, new Binding() { Path = new PropertyPath(YOUR_BINDING_PROPERTY_PATH) });
ItemTemplate.VisualTree = textblockElement2;
comboboxElement.SetValue(ComboBox.ItemTemplateProperty, ItemTemplate);
stackpanelElement.AppendChild(comboboxElement);
DT.VisualTree = stackpanelElement;
return MultiValueTemplate;
}

How to attach extra data to listbox items in VB.Net

i'm new to visual studio and working on a project in visual basic. I am additing data to listbox from database that i need to access later.. Can we add extra data to listbox items as we can do with following html ??
<option name="harry" age="10" value="1">My name is harry</option>
any ideas please ???
regards
You don't "attach" any data (whatever that means) to any UI elements in WPF, simply because UI is not Data.
If you're working with WPF, you really need to understand The WPF Mentality, which is very different from other approaches used in other technologies.
In WPF, you use DataBinding to "Bind" the UI to the Data, as opposed to "putting" or "storing" the data in the UI.
This is an example of how you Bind a ListBox to a collection of data items in WPF:
XAML:
<ListBox ItemsSource="{Binding MyCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel:
public class MyViewModel
{
public ObservableCollection<MyData> MyCollection {get;set;}
//methods to create and populate the collection.
}
Data Item:
public class MyData
{
public string LastName {get;set;}
public string FirstName {get;set;}
}
I strongly suggest that you read up on MVVM before starting to code in WPF. Otherwise you'll hit walls quickly and waste too much time in unneeded code.

WPF: ListView with Images from a folder

I'm sort of new to WPF, but I have to do this and it's taking a lot of my time. I've searched for a solution but there are many alternative solutions and I honestly don't understand most of this. I have this XAML code:
<ListView Name="Thumbnails">
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Height="30" Width="30" Margin="5"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
As well as this codebehind:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DirectoryInfo folder = new DirectoryInfo(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) + #"\SlikeSportista\");
FileInfo[] images = folder.GetFiles("*.jpg");
foreach (FileInfo img in images)
{
Thumbnails.Items.Add(img);
}
}
I've also tried this line of code in the foreach loop:
Thumbnails.Items.Add(System.Drawing.Image.FromFile(img.FullName));
In both cases the items are added, but the images are not displayed correctly, or rather, at all. You can select them, and there are the same amount of elements as there are in the folder, but there is no display.
Another question (less important one) would be how to display the images in squares instead of rows. Basically I want to have about 4 or so images per row, but now I have only 1 element per row, stretched all the way (although I can't see what is being displayed).
In your first attempt, you're adding FileInfo objects to the ListView's items collections. These aren't automatically converted to ImageSource items, as required by the binding in your DataTemplate. Add the FileInfo's FullName instead:
foreach (FileInfo img in images)
{
Thumbnails.Items.Add(img.FullName);
}
In your second attempt the problem is that you add instances of System.Drawing.Image, which is not part of WPF, but WinForms, and will also not be converted automatically. You may use BitmapImage instead:
foreach (FileInfo img in images)
{
Thumbnails.Items.Add(new BitmapImage(new Uri(img.FullName)));
}
The difference between both solutions is that in the second one you manually create image objects, whereas the first one relies on automatic conversion from string to ImageSource, which is built into WPF as a TypeConverter.
A solution for your second question would be to replace the ListView's ItemsPanel, perhaps by a UniformGrid:
<ListView Name="Thumbnails">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="4"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
...
</ListView>
As a general rule, you should keep in mind that adding images in the code-behind file (.xaml.cs file) is bad practice. In WPF there is a very widely used and common design pattern called MVVM (Model-View-ViewModel) which you should familiarize with and use. In your case, you should have had a ViewModel class containing an IEnumerable<BitmapImage> property that contains the images you wish to display in your ListView.
For example, let's say your ViewModel class is called ImagesViewModel and your view is ImagesView:
ImagesViewModel will have a property called:
ObservableCollection<BitmapImage> Images
ImagesView will contain:
<ListView Name="Thumbnails" ItemsSource="{Binding Images}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="4"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
...
Now, if you add / remove images to Images, they will be automatically added / removed from your list view (you have to implement the INotifyPropertyChanged interface in your view model and you're done).

How to dynamically add MenuItems (with a header) to a WPF menu

[Edit #3] - to anyone reading this question: do not under any circumstance use the approach outlined in this question. It is a Coding Horror. I freely admit this, knowing that all programmers have worked themselves into a corner in the past, and (especially when learning a new technology) we all have been led astray by other, well-meaning developers on the interweb. Read the answer by Robert first, then read this question. Please.
[Edit #2b]
I apologize for the length of this question - there is a question in here (at the end!), but I wanted to make sure the source code was explicit. Anyway.
[Edit #2] - question title changed to more accurately reflect the... question.
[Edit] - I've updated some more of the history as to how I ended up at the design / code that I did here: Obligatory Blog Post. If it helps clarify the question below, feel free to read it...
Original question
The application I'm working on uses Prism and WPF, with a number of modules (currently 3), one of which hosts the application menu. Originally, the menu was static with hooks into CompositeCommand / DelegateCommands, which worked great for routing button presses to the appropriate presenter. Each MenuItem used a StackPanel in its header to display the content as a combination of an image and a text label - which was the look I was going for:
<Menu Height="48" Margin="5,0,5,0" Name="MainMenu" VerticalAlignment="Top" Background="Transparent">
<MenuItem Name="MenuFile" AutomationProperties.AutomationId="File">
<MenuItem.Header>
<StackPanel>
<Image Height="24" VerticalAlignment="Center" Source="../Resources/066.png"/>
<ContentPresenter Content="Main"/>
</StackPanel>
</MenuItem.Header>
<MenuItem AutomationProperties.AutomationId="FileExit" Command="{x:Static local:ToolBarCommands.FileExit}">
<MenuItem.Header>
<StackPanel>
<Image Height="24" VerticalAlignment="Center" Source="../Resources/002.png"/>
<ContentPresenter Content="Exit"/>
</StackPanel>
</MenuItem.Header>
</MenuItem>
</MenuItem>
<MenuItem Name="MenuHelp" AutomationProperties.AutomationId="Help" Command="{x:Static local:ToolBarCommands.Help}">
<MenuItem.Header>
<StackPanel>
<Image Height="24" VerticalAlignment="Center" Source="../Resources/152.png"/>
<ContentPresenter Content="Help"/>
</StackPanel>
</MenuItem.Header>
</MenuItem>
</Menu>
Unfortunately, the application has gotten a bit more complex and it is desireable to have other modules register themselves with the menu - hence, I've been looking at making the menu dynamic. The goal is to have other modules (through a service) be able to add commands to the menu at will - for example, Module A will add a menu item in the Toolbar module that calls a handler in Module A. There's a few excellent articles out there on this subject - the two I've looked at are Building a Databound WPF Menu Using a HierarchicalDataTemplate and WPF Sample Series - Databound HierarchicalDataTemplate Menu Sample. Following the advice in the article, I have managed to make a dynamically constructed menu with no obvious data binding problems - it can create a menu with items linked backed to my presentation model, reflecting the structure of an ObservableCollection in the presentation model
Currently, my XAML looks like the following:
<UserControl x:Class="Modules.ToolBar.Views.ToolBarView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:Modules.ToolBar.PresentationModels"
xmlns:local="clr-namespace:Modules.ToolBar">
<UserControl.Resources>
<model:ToolBarPresentationModel x:Key="modelData" />
<HierarchicalDataTemplate DataType="{x:Type model:ToolbarObject}"
ItemsSource="{Binding Path=Children}">
<ContentPresenter Content="{Binding Path=Name}"
Loaded="ContentPresenter_Loaded"
RecognizesAccessKey="True"/>
</HierarchicalDataTemplate>
</UserControl.Resources>
<UserControl.DataContext>
<Binding Source="{StaticResource modelData}"/>
</UserControl.DataContext>
<Menu Height="48" Margin="5,0,5,0" Name="MainMenu" VerticalAlignment="Top" Background="Transparent"
ItemsSource="{Binding}">
</Menu>
</Grid>
</UserControl>
The code behind for the view does the heavy lifting in the ContentPresenter_Loaded method:
private void ContentPresenter_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
ContentPresenter presenter = sender as ContentPresenter;
if (sender != null)
{
DependencyObject parentObject = VisualTreeHelper.GetParent(presenter);
bool bContinue = true;
while (bContinue
|| parentObject == null)
{
if (parentObject is MenuItem)
bContinue = false;
else
parentObject = VisualTreeHelper.GetParent(parentObject);
}
var menuItem = parentObject as MenuItem;
if (menuItem != null)
{
ToolbarObject toolbarObject = menuItem.DataContext as ToolbarObject;
StackPanel panel = new StackPanel();
if (!String.IsNullOrEmpty(toolbarObject.ImageLocation))
{
Image image = new Image();
image.Height = 24;
image.VerticalAlignment = System.Windows.VerticalAlignment.Center;
Binding sourceBinding = new Binding("ImageLocation");
sourceBinding.Mode = BindingMode.TwoWay;
sourceBinding.Source = toolbarObject;
image.SetBinding(Image.SourceProperty, sourceBinding);
panel.Children.Add(image);
}
ContentPresenter contentPresenter = new ContentPresenter();
Binding contentBinding = new Binding("Name");
contentBinding.Mode = BindingMode.TwoWay;
contentBinding.Source = toolbarObject;
contentPresenter.SetBinding(ContentPresenter.ContentProperty, contentBinding);
panel.Children.Add(contentPresenter);
menuItem.Header = panel;
Binding commandBinding = new Binding("Command");
commandBinding.Mode = BindingMode.TwoWay;
commandBinding.Source = toolbarObject;
menuItem.SetBinding(MenuItem.CommandProperty, commandBinding);
}
}
}
As you can see, I'm attempting to recreate the StackPanel / Image / Name combination of the original menu, just doing so in the code behind. Attempting to do this has not worked out so well - while the menu objects are certainly being created, they don't "appear" as anything other than blank, clickable objects - the StackPanel, Image, Name, etc. aren't being rendered. Interestingly enough, it also is causing the original text in the ContentPresent in the HierarchicalDataTemplate to be erased.
The question then, is there a way to set a MenuItem's Header property in the Load event such that it will display on the UserControl properly? Is the fact that the items in the header are not being displayed indicative of a DataBinding problem? If so, what would be the proper way to bind the Header to a transient object (the StackPanel that was created in the load event handler)?
I'm open to changing anything in the code above - this is all sort of prototyping along, trying to figure out the best way to handle dynamic menu creation.
Thanks!
I'll confess that I haven't dug quite as deep into your example as maybe I should, but whenever I see code-behind that's searching the visual tree, I think, could this be handled more explicitly in a view model?
It seems to me in this case that you could come up with a pretty straightforward view model - an object exposing Text, Image, Command, and Children properties, for instance - and then create a simple data template that for presenting it as a MenuItem. Then anything that needs to alter the contents of your menus manipulates this model.
Edit:
Having looked at what you're up to in more detail, and the two examples you've linked to in your blog post, I am banging my head against the desk. Both of those developers appear to be under the misapprehension that the way to set properties on the menu items that are being generated by the template is to search through the visual tree in the ContentPresenter.Load event after they're created. Not so. That's is what the ItemContainerStyle is for.
If you use that, it's quite straightforward to create dynamic menus of the type you're describing. You need a MenuItemViewModel class that has INotifyPropertyChanged implemented and exposes these public properties:
string Text
Uri ImageSource
ICommand Command
ObservableCollection<MenuItemViewModel> Children
Using this:
<Menu DockPanel.Dock="Top" ItemsSource="{DynamicResource Menu}"/>
where the ItemsSource is an ObservableCollection<MenuItemViewModel>, and using this template:
<HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}"
ItemsSource="{Binding Path=Children}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Command}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}" />
<Label Content="{Binding Text}" />
</StackPanel>
</HierarchicalDataTemplate>
the menus in the window exactly represent what's in the collection, and are dynamically updated as items are added and removed, both to the top-level items and to the descendants.
There's no clambering about in the visual tree, no manual creation of objects, no code-behind (other than in the view model, and in whatever populates the collection in the first place).
I've built a pretty thoroughly worked example of this; you can download the project here.
Another possible approach could be having the Menu be a region and agree on a convention so all views added to that region have a ViewModel with a property named MenuHeader. That way, the region adapter can simply get the menu header from the View's Data Context, and set it to the item when adding it.
Something similar is done in Prism with views added to a Tab Region. You can read more here.
I hope this provides some useful guidance.
Thanks,
Damian

ListBox not populating on data bind in Silverlight 2

So I'm trying to learn Silverlight so I've built a simple demo app that pulls my home feed from FriendFeed and displays the items in a list.
I've got a listbox defined:
<ListBox x:Name="lstItems" Margin="5,61,5,5" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="8,8,43,8">
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
which is being populated by a web service call
private void LoginButton_Click(object sender, RoutedEventArgs e)
{
FriendFeedServiceClient client = new FriendFeedServiceClient();
client.GetHomeCompleted += new EventHandler<GetHomeCompletedEventArgs>(client_GetHomeCompleted);
client.GetHomeAsync(FfUsername.Text, FfApiKey.Password);
}
void client_GetHomeCompleted(object sender, GetHomeCompletedEventArgs e)
{
lstItems.DataContext = e.Result;
}
The FriendFeedServiceClient is doing a call to a local webservice that proxies a request to the actual FriendFeed webservice.
The service call works fine, the items are returned, if I debug the call the lstItems.DataContext property is populated with a list of items with data in them, but the list doesn't display anything, it's always blank. Have I missed something?
You need to bind your Listbox, something like this
<ListBox x:Name="lstItems" Margin="5,61,5,5" Grid.Row="1" ItemsSource="{Binding}">
and then the TextBlock's binding to the path Title should work.
EDIT: You are setting the DataContext, which kind of gives a hint that you are probably binding a custom object, have you tried casting the e.GetResult to your custom object,
something to the likes of
YourCustomObject obj = (YourCustomObject) e.GetResult;
lstItems.DataContext = obj;
HTH
Rather than DataContext you should be setting ItemsSource. If you use DataContext then you have to set ItemsSource with a binding, however, this level of indirection is rather unnecessary for what you're trying to do.
See this MSDN article for details on listing data in the ListBox.
You're not binding to DataContext.
Try adding ItemsSource="{Binding}":
<ListBox x:Name="lstItems" Margin="5,61,5,5" Grid.Row="1" ItemsSource="{Binding}">
Then make sure that both class and Title property of your object are not private.
Also check output (int output window in visual studio) if there are any Binding error messages and let us know.

Resources