How to set the background image to the dynamically created button control? - silverlight

I am developing window phone 7 application. I am new to the window phone 7 application. I have Listbox in my application & I am dynamically creating the button control and adding them to the listbox control. Now I want to set the background image for each dynamically created button control. I am using the following code to dynamically set the background color of button control
Button AlphabetButton = new Button();
AlphabetButton.Background = new SolidColorBrush(Colors.Red);
In this way instead of background color I want to set the background image for my dynamically created button control. How to do this ? Can you please provide me any code or link through which I can resolve the above issue. If I am doing anything wrong then please guide me.

Try this:
ImageBrush brush = new ImageBrush();
brush.ImageSource = new BitmapImage(new Uri(#"Images/myImage.png", UriKind.Relative));
AlphabetButton.Background = brush;
Another option is to use the Button Content like for example:
Uri uri = new Uri("/images/someImage.png", UriKind.Relative);
BitmapImage imgSource = new BitmapImage(uri);
Image image = new Image();
image.Source = imgSource;
AlphabetButton.Content = image;
Be careful with the image Build Action.

A much better approach would be to use databinding ... say for example you have a list of Person objects that you want to display in your listbox, in codebehind set the list as the ItemsSource:
public class Person()
{
public string Name {get; set;}
}
var persons = new List<Person>();
// ... add some person objects here ...
listBox.ItemsSource = persons
In you XAML you can then supply a DataTemplate that renders each person as a Button:
<ListBox x:Name="listBox">
<Listox.ItemTemplate>
<DataTemplate>
<Button Content={Binding Path=Name}/>
</DataTemplate>
</Listox.ItemTemplate>
</ListBox>
This will render a list of Buttons which display the name of each person.
To specify an image for each button extend the content of your button to include an image:
<ListBox x:Name="listBox">
<Listox.ItemTemplate>
<DataTemplate>
<Button>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}"/>
<ImageText Source="{Binding Path=Image}"/>
</StackPanel>
</Button>
</DataTemplate>
</Listox.ItemTemplate>
</ListBox>
You will of course have to add an ImageSource property to your Person object:
public class Person()
{
public string Name {get; set;}
public ImageSource Image {get; set;}
}
Another alternative is to use a value converter to convert some property of your Person into an image:
Dynamic image source binding in silverlight
If you do not want to use binding (which I personally think is the best approach) you could create a Button subclass that has an ImageSource property as per the following:
Creating an image+text button with a control template?

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;
}

WPF - dynamically display Image with Text above

in my .net4-0 C# application I have a grid of 8x5 buttons. Each of them should display an image and a single letter (the hotkey to press) over the image (top left corner). Because the images depend on my data, it must be dynamically. All Images have the same size (100x100 pixel). The Image should fill the button nicely.
How can i achieve this?
My thoughts so far is, every time I load my data and change the display images, I manually create a Stackpanel, with a Image and TextBlock on it. But this doesn't fit well together.
Use a ListBox and put your list of data into ListBox.ItemSource Then you can create your own DataTemplate do specify how you want to display your data in that ListBox
For Example you can specify that you want your ListBox to be displayed with a 8x5 Grid. This of course depends on if you know that you will always display your Grid with 8x5 cells.
For the specific that you want to have a button with a letter on top as you said I would go with this.
<!--Resource-->
<DataTemplate DataType={x:YourViewModel}>
<StackPanel>
<TextBlock Content="{binding SomeText}"/>
<Button>
<Button.Content>
<Image Source="{binding YourImageSource}" Width="100px" Height="100px"/>
</Button.Content>
</Button>
</StackPanel>
</DataTemplate>
This is assuming that you're using MVVM which is highly recommended when working with WPF.
If you don't know what MVVM then start reading about it cause it will help your development in WPF to become so much better.
If my example is confusing, please provide feedback and I will make it more clearer.
EDIT Simple-MVVM Example
Let's say we want to display a Game with a title and picture
First we create the Model
public class Game
{
private string _title {get; set;}
private string _imagepath {get; set;} //We are not getting the image but the imagepath
Public string Title {get{return _title;} set{_title = value;}
Public string Imagepath set{_imagepath = value;}
}
Then we need a ViewModel. Normally the ViewModel doesn't create new data since the data should come from the Model (From maybe a Database), but for the sake of this example we create it in the ViewModel
public class GameViewModel
{
Public ObservableCollection<Game> Games {get; set;} //<--- This is where the View is binding to
public GameViewModel
{
ObservableCollection<Game> Games = new ObservableCollection<Game>(); //ObservableCollection is used to notify if we have added or removed or updated an item in the list.
Games.Add(new Game() {Title = "Warcraft 3", Image=//Where you have your images );
Games.Add(new Game() {Title = "Overwatch", Image=//Where you have your images );
this.Games = Games;
}
}
And now when to have our View to bind to this ViewModel in our XAML-code
<!--Basic xaml-->
<!--Resource-->
<DataTemplate DataType={x:Type local:Game}>
<StackPanel>
<TextBlock Content="{Binding Title}"/>
<Button>
<Button.Content>
<Image Source="{Binding Imagepath}" Width="100px" Height="100px"/>
</Button.Content>
</Button>
</StackPanel>
</DataTemplate>
Then in our ListBox
<ListBox ItemSource="{Binding Games}"/>
To get this to work you need to set the Datacontext in the View. Often when you create a new WPF-project the the View is called MainWindow.xaml
Add the ViewModel to the datacontext like this
/*Normally you want to avoid doing anything in code-behind with MVVM
If you want to avoid that you have to look into DI and IoC But it is way
to much to do in this example*/
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new GameViewModel();
}
}

Changing content changes DataContext when using DataTemplate

X problem.
I want to enlarge (let it take whole available space) a part of window content temporarily.
Window layout is pretty complicated: many nested panels, splitters, content to enlarge is 10 levels deep. Changing Visibility to stretch content is simply not enough (thanks to splitters) and seems very complicated.
Y problem
I decide to move that content into a user control and do something like (pseudo-code)
if(IsEnlarged)
{
oldContent = window.Content; // store
window.Content = newContent;
}
else
window.Content = oldContent; // restore
No problems. It was working perfectly in test project ... until I start using data templates.
Problem: if data templates are used then as soon as window.Content = newContent occurs, then that newContent.DataContext is lost and become the same as window.DataContext. This will trigger various binding errors, attached behaviors suddenly changes to default value, etc.. all kind of bad stuff.
Question: why DataContext is changing? How to prevent/fix this issue?
Here is a repro (sorry, can't make it any shorter):
MainWindow.xaml contains
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel}">
<local:UserControl1 />
</DataTemplate>
</Window.Resources>
<Grid Background="Gray">
<ContentControl Content="{Binding ViewModel}" />
</Grid>
MainWindow.cs contains
public ViewModel ViewModel { get; } = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
UserControl1.xaml contains
<Button Width="100"
Height="100"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Command="{Binding Command}" />
ViewModel (using DelegateCommand)
public class ViewModel
{
public DelegateCommand Command { get; set; }
bool _set;
object _content;
public ViewModel()
{
Command = new DelegateCommand(o =>
{
var button = (Button)o;
var window = Window.GetWindow(button);
_set = !_set;
if (_set)
{
_content = window.Content;
var a = button.DataContext; // a == ViewModel
window.Content = button;
var b = button.DataContext; // b == MainWindow ??? why???
}
else
window.Content = _content;
});
}
}
Set breakpoint on var a = ..., start program, click the button, do steps and observe button.DataContext as well as binding error in Output window.
ok here some general thoughts.
if you bind a object(viewmodel) to the Content property of the contentcontrol then wpf uses a DataTemplate to visualize the object. if you dont have a DataTemplate you just see object.ToString(). DataContext inheritance means that the DataContext is inherited to the child elements. so a real usercontrol will inherit the DataContext from the parent. the are common mistakes you find here on stackoverflow when creating UserControls - they often break DataContext inheritance and set the DataContext to self or a new DataContext.
UserControl DataContext Binding
You must be trying to use your DataTemplate as ContentTemplate for your ContentControl. As ContentTemplate operates on Content, so it will use Content as its DataContext. And your Content contains ViewModel.
Once your Button is no longer part of your DataTemplate, so it will use MainWindow's DataContext.
No seeing your comments, I am assuming that you want DataContext of UserControl to remain intact, even if your UserControl is not part of DataTemplate.
So, simple set DataContext of Button explicitly using XAML using RelativeSource.
Example,
<Button Content="{Binding Data}" DataContext="{Binding vm1, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" />
Playing with DataContext in code is not a good idea.

How to bind text box values to property of SelectedItem in ListBox

Sorry, XAML is new for me, so I apologize in advance if I sound like a noob.
I have items in the ListBox being dynamically added/deleted/etc, it is bound like so:
<ListBox Name="missionList" ItemsSource="{Binding Mode=OneWay}" ItemTemplate="{DynamicResource missionLegTemplate}" SelectionChanged="missionList_SelectionChanged"/>
The items it is bound to are contained in a list that inherits from ObservableList. This list is my DataContext which is set at the start of the program:
public MainWindow()
{
InitializeComponent();
DataContext = new MissionList();
}
I have a series of text boxes which are intended to be used both to display current values of the object properties and save them as they are edited.
After reading around, I am getting the impression that I need to set the DataContext in the scope of the text boxes to the currently selected object in the ListBox, but I'm not sure how to access that object with XAML.
Does that make sense? Thanks in advance for the help!
I would probably move your MissionList into a ViewModel class, along with a new property for SelectedMission that can be bound to the ListBox's SelectedItem.
As an example (not necessarily working code):
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
public class MainWindowViewModel
{
public ObservableCollection<Mission> MissionList { ... }
public Mission SelectedMission { ... }
.
.
.
}
<ListBox ItemsSource="{Binding MissionList}" SelectedItem="{Binding SelectedMission}" />
<TextBox Text="{Binding Path=SelectedMission.SomeTextField, Mode=OneWay}" />

Making an easy to use UserControl via Properties

in my Silverlight 4 app, I try to create a simple UserControl, which will be consumed by my Application. To keep things simple, it shall have a "header" and a placeholder, where I want to place any kind of control.
<User Control ...>
<Grid x:Name="LayoutRoot">
<TextBlock x:Name="TextBlockHeader" Text="{Binding Title}" />
<ContentPresenter x:Name="ContentPresenterObject" />
</Grid>
</UserControl>
In the code behind, I have created a property for the text of the TextBlock
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(MyAccordion), null);
This way, I can set the Title property, when I use the Control in my application.
<local:MyAccordion Title="Test"/>
But it seems, that the binding at the textblock Text="{Binding Title}" doesn't make the text "Test" to be displayed as the textblocks text.
My question is: How can I make the Property Title to be displayed as the textboxes text and how do I do this for the - any type of user control containable - contencontrol?
Thanks in advance,
Frank
Maybe DataContext of control or page was not set. - First of all you should read more about a Binding ("http://www.silverlight.net/learn/data-networking/binding/data-binding-to-controls-(silverlight-quickstart)"). If you are working on real project and will design a some arhitecture, you should read about MVVM pattern.
The answer is ElementPropertyBinding. I need to reference the User Control in the Binding or add the binding in the constructor.
Create the binding in XAML:
<User Control ... x:Name="userControl">
...
<TextBlock x:Name="TextBlockHeader" Text="{Binding Title, ElementName=userControl}" />
</UserControl>
Create the binding in the constructor (Code behind)
public MyUserControl()
{
// Required to initialize variables
InitializeComponent();
TextBlockHeader.SetBinding(TextBlock.TextProperty, new System.Windows.Data.Binding() { Source = this, Path = new PropertyPath("Title") });
}
I still need to find out how to add a child control, but that's another question.

Resources