I am trying to add the button to my DockPanel dynamically. I need to create the same button which exist in my dockpanel.
<Button Name="ImageMoreButton"
DockPanel.Dock="Right"
Command="{Binding LaunchLookup}"
Style="{StaticResource ButtonStyle}"
Margin="2,0,2,0"
Padding="3"
Visibility="{Binding Definition.IsLookupImageButton, Converter={StaticResource boolToVisibilityConverter}}"
IsEnabled="{Binding Locked, Converter={StaticResource invertedBooleanConverter}}">
<Image Name="button_image" Source="search_button_rest.png"/>
</Button>
Here is my C# code.
d.Name = VariableArg.Name + index;
d.Margin = VariableArg.Margin;
item.Command = ImageMoreButton.Command;
item.Style = ImageMoreButton.Style;
item.Name = ImageMoreButton.Name + index;
item.Visibility = ImageMoreButton.Visibility;
item.Padding = ImageMoreButton.Padding;
item.Margin = ImageMoreButton.Margin;
item.IsEnabled = ImageMoreButton.IsEnabled;
item.Height = ImageMoreButton.ActualHeight;
item.Width = ImageMoreButton.ActualWidth;
DockPanel.SetDock(item, Dock.Right);
Let me know if this is the correct way to that.
WPF Controls cannot be added to two different parent controls. If you wish to add a copy of an item at runtime, you need to create a new object entirely, not re-use an existing item.
That said, since your buttons represent a Configuration setting, I would recommend you use something like an ItemsControl that is bound to a collection of data objects, with the Button being used as the ItemTemplate.
For example, suppose you had an ObservableCollection<MySetting> collection called Settings. You could then write the following XAML:
<ItemsControl ItemsSource="{Binding Settings}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- ItemContainerStyle -->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="DockPanel.Dock" Value="Right" />
</Style>
</ItemsControl.ItemContainerStyle>
<!-- ItemTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding DataContext.LaunchLookup, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
Style="{StaticResource ButtonStyle}"
Margin="2,0,2,0"
Padding="3"
Visibility="{Binding Definition.IsLookupImageButton, Converter={StaticResource boolToVisibilityConverter}}"
IsEnabled="{Binding Locked, Converter={StaticResource invertedBooleanConverter}}">
<Image Source="search_button_rest.png"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then to add new items, you would simply add items to the ObservableCollection
Settings.Add(new MySetting());
For other examples using an ItemsControl, check out this post I wrote
If you use this code a lot I suggest you define an extension method on Button class. like this :
public static class ButtonExtension
{
public static Button Clone(this Button myButton, int index)
{
var newButton = new Button
{
Command = myButton.Command,
Style = myButton.Style,
Name = myButton.Name + index,
Visibility = myButton.Visibility,
Padding = myButton.Padding,
Margin = myButton.Margin,
IsEnabled = myButton.IsEnabled,
Height = myButton.ActualHeight,
Width = myButton.ActualWidth
};
return newButton;
}
}
which you can use later like :
var newButton = ImageMoreButton.Clone(index);
DockPanel.SetDock(newButton, Dock.Right);
Talking about "a better way", I recommend you to get faimliar with the MVVM Pattern since it's very powerful in simplifying the UI code. Also, it seems to be a widespread best practice for WPF programming since WPF has great infrastructure for it. There certainly is a learning curve, but once you understand it, you'll solve such puzzles easily. I could provide a sample, but I'm not sure that it would be useful here.
If you need a solution right now, you can use your code, it seems to be fine. But don't forget to add new controls to the parents:
parentPanel.Children.Add(item);
Related
I am trying to display some images in a ListView and have not been successful. I am using WPF MVVM and the ListView is a holdover from simply displaying suits and rank data. (See my previous post: MVVM in WPF - How to alert ViewModel of changes in Model... or should I? if you are interested!) That is, I could use something other than ListView (if that is the advice) but I would still like to know how to do it with ListView, assuming it's doable. My property I'm binding to in the ViewModel is:
public ObservableCollection<Image> PlayerCardImages{
get{
ObservableCollection<Image> results = new ObservableCollection<Image>();
foreach (CardModel card in PlayerCards)
{
Image img = new Image();
BitmapImage bi3 = new BitmapImage();
bi3.BeginInit();
// TODO: Pick card based on suit/rank. Just get 1 image working now
bi3.UriSource = new Uri("diamond-1.png", UriKind.Relative);
bi3.EndInit();
img.Stretch = Stretch.Fill;
img.Source = bi3;
results.Add(img);
}
return results;
}
}
In my XAML code I'm using:
<Window.Resources>
<DataTemplate x:Key="ImageCell">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding PlayerCardImages}" Width="200" Height="200" Stretch="Fill" ToolTip="Add tooltip"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel Orientation="Vertical">
<Label Content="Player Cards"/>
<ListView Name="lvwTitles" ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True"
SelectionMode="Single" ItemTemplate="{StaticResource ImageCell}" Height="59">
</ListView>
</StackPanel>
This idea was shamelessly stolen from: WPF - bind images horizontally to ListView However, it doesn't even appear to databind as evidenced by my breakpoint in PlayerCardImages not being hit.
I also tried the following XAML with somewhat better luck:
<StackPanel Orientation="Vertical">
<Label Content="Player Cards"/>
<ListView
AlternationCount="2"
DataContext="{StaticResource PlayerCardsGroups }"
ItemsSource="{Binding}"
>
<ListView.GroupStyle>
<StaticResourceExtension
ResourceKey="CardGroupStyle"
/>
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn>
<Image Height="50" Width="40"></Image>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
<Window.Resources>
<CollectionViewSource x:Key="PlayerCardsGroups"
Source="{Binding Path=PlayerCardImages}">
</CollectionViewSource>
<GroupStyle x:Key="CardGroupStyle">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock
x:Name="txt"
Background="{StaticResource Brush_HeaderBackground}"
FontWeight="Bold"
Foreground="White"
Margin="1"
Padding="4,2,0,2"
Text="Cards"
/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
<Style x:Key="CardItemStyle" TargetType="{x:Type ListViewItem}">
<!--
Stretch the content of each cell so that we can
right-align text in the Total Sales column.
-->
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<!--
Bind the IsSelected property of a ListViewItem to the
IsSelected property of a CustomerViewModel object.
-->
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="ItemsControl.AlternationIndex" Value="1" />
<Condition Property="IsSelected" Value="False" />
<Condition Property="IsMouseOver" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="#EEEEEEEE" />
</MultiTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
this code definitely goes through databinding - my breakpoint is hit at the beginning of the program and whenever items are added to the collection. But no images is displayed. Rather than torture you with more XAML that does not work, perhaps I could ask someone to point me to some code/examples/docs that show how to bind a list of Images to a ListView (or another control if you really feel that ListView is inappropriate). Notice that my collection is the stuff I'm binding to. I notice that with many examples, they are binding to a subproperty. I.e. they may have a collection of albums and for each album they bind to it's property image (see: Showing items as images in a WPF ListView).
Any ideas or help would be much appreciated.
-Dave
Additional info.
Based on suggestions by Clemens, I now have this code for PlayerCardImages:
public ObservableCollection<ImageSource> PlayerCardImages
{
get
{
var results = new ObservableCollection<ImageSource>();
//if (PlayerCards.Count == 0)
// return results;
//else
//{
// results.Add(new BitmapImage(new Uri(#"Images\\" + "diamond-1.png", UriKind.Relative)));
//}
foreach (var card in PlayerCards)
{
results.Add(new BitmapImage(new Uri(#"Images\\" + GetCardFileName(card), UriKind.Relative)));
}
return results;
}
I used the exact XAML he suggested. It almost works. I say "almost" because I noticed strange behavior whereby sometimes 1 card would show and sometimes not (I never got 2 cards). All the getting cards from files and binding seems to be working and I tracked down what I think is key to the last remaining bug (and it's BIZARRE). If in the debugger, I examine results, and further open up results[0] in the debugger, I get that card displayed! I actually have to open up [0] (you see info about height, width, etc.) for this to work. Furthermore if I open up [1], I get that card displayed instead. Why would opening up the debugger info have any effect? For those of you who might ask, what happens if you open up both cards in the debugger... that doesn't work. I get a operation timed out exception. I will say that perhaps my image files are big. 10Kbytes to 30 Kbytes. Is that the problem? I'm guessing not, and that it's a subtle problem with reading in the images or binding. What is going on? Thanks, Dave
First, you should not use Image controls in your ViewModel. You already have an Image control in the DateTemplate of your view. You want to bind the Source property of this Image conntrol, and the source property of this binding can't be another Image.
Instead your ViewModel would either use ImageSource (or a derived class like BitmapImage) as image type, like this:
public ObservableCollection<ImageSource> PlayerCardImages
{
get
{
var results = new ObservableCollection<ImageSource>();
foreach (var card in PlayerCards)
{
results.Add(new BitmapImage(new Uri(card.ImageUrl)));
}
return results;
}
}
Or simply the image URIs or paths, as there is an automatic conversion from string to ImageSource built into WPF:
public ObservableCollection<string> PlayerCardImages
{
get
{
var results = new ObservableCollection<string>();
foreach (var card in PlayerCards)
{
results.Add(card.ImageUrl);
}
return results;
}
}
You would now bind your Listbox's ItemsSource property to the PlayerCardGames collection, and in the DataTemplate you bind directly to the collection item. The ListView's DataContext has to be set to an instance of the object that defines the PlayerCardGames property.
<ListView ItemsSource="{Binding PlayerCardGames}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
UPDATE: As there seems to be a problem with loading the image files, you may try the following method. It loads images synchronously and you are able to step through with the debugger.
public static ImageSource LoadImage(string fileName)
{
var image = new BitmapImage();
using (var stream = new FileStream(fileName, FileMode.Open))
{
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
}
return image;
}
You can use this method in your PlayerCardGames property getter like this:
foreach (var card in PlayerCards)
{
results.Add(LoadImage(#"Images\\" + GetCardFileName(card)));
}
I didn't really try to reproduce your problem, but I will if this is not solving it:
In your first xaml block, I think you mixed up the bindings. This would be how I expect it, ItemsSource to the ObservableCollection of Images, Image source to the Image.
<Image Source="{Binding}" ... />
<ListView Name="lvwTitles" ItemsSource="{Binding PlayerCardImages}" ... />
In your second block, you omitted the Source binding altogether:
<Image Source="{Binding}" Height="50" Width="40" />
I'm working on a WPF page with the following:
<ItemsControl ItemsSource="{Binding Peopl.PhoneNums}" x:Name="PhoneList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<StackPanel Orientation="Horizontal" Margin="0,0,0,0" x:Name="PhoneEntry">
<TextBlock Text="123-456-78901"/>
<ComboBox ...>
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
There can be multiple stackpanels, each with a unique phone number; in code behind, each phone number has a flag indicating if it should be enabled; I want to be able to enable each entry in the stack panel based on that flag but I'm stuck accessing it....
I have:
foreach (Phone phone in PhoneList.ItemsSource)
{
if (phone.ShouldBeDisabled)
{
int index = PhoneList.Items.IndexOf(phone);
PhoneList.IsEnabled = false;
//this disables the entire control;
// I can't access "PhoneEntry" here... hmm
}
}
Is there a way to disable only one entry at a time? How can I access PhoneEntry? Should I try to disable the each stackpanel entry based on the bound value?
You may invert your view model property and call it ShouldBeEnabled. Now you can bind the StackPanel's IsEnabled property.
<StackPanel ... IsEnabled="{Binding ShouldBeEnabled}">
...
</StackPanel>
In case you can't change the view model, you may use a binding converter that inverts the property value:
<StackPanel ... IsEnabled="{Binding ShouldBeDisabled,
Converter={StaticResource InverseBooleanConverter}}">
...
</StackPanel>
Your Phone class would have to implement the INotifyPropertyChanged interface and fire the PropertyChanged event when the value of the ShouldBeDisabled property changes.
I'm new to MVVM and I'm working on a WP8 app and I'd like to be able to set the visibility of buttons and a textblock based on when one of those buttons were tapped. Here's my View to try and explain my problem a bit better; (http://i.imgur.com/JvrxBkh.png - can't post an image on this reputation) .
When the user taps the "Going to sleep" button, I'd like the counter textblock and the "I'm awake" button to be visible with the "Going to sleep" button to be collapsed. It'll then work the other way once the "I'm awake" button is pressed, etc. If I wasn't using MVVM I'd just set the Visibility value inside the button event, but I'm stuck on how to do this when using the MVVM pattern.
I've looked around and come across a solution using a converter such as using a BooleanToVisibilityConverter class and a bool property and then setting the visibility by binding to the bool value from the ViewModel and setting the converter value for the visibility to the StaticResource BooleanToVisibilityConverter. But it just doesn't work for me the way I want. Then my counter textblock has a bind already from the ViewModel so would I need some kind of multi-binding for this textblock?
Hopefully I've explained myself OK. It seems like it should be a simple task that maybe I'm just over thinking or something.
EDIT With some code snippets
The View components that I was referring to;
<BooleanToVisibilityConverter x:Key="boolToVis" />
<TextBlock
Grid.Row="2"
Text="{Binding Counter}"
FontSize="50"
TextWrapping="Wrap"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding VisibilityControl, Converter={StaticResource boolToVis}}"/>
<Button
Grid.Row="3"
Width="230"
Height="70"
Content="I'm awake"
BorderThickness="0"
Background="Gray"
Margin="0,20,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Command="{Binding AwakeButtonCommand}"
Visibility="{Binding VisibilityControl, Converter={StaticResource boolToVis}}""/>
<Button
Grid.Row="3"
Width="230"
Height="70"
Content="Going to sleep"
BorderThickness="0"
Background="Gray"
Margin="0,20,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Command="{Binding SleepButtonCommand}"
Visibility="{Binding VisibilityControl, Converter={StaticResource boolToVis}}"/>
Then in the ViewModel VisibilityControl is just;
private bool _visibilityControl;
public bool VisibilityControl
{
if (_visibilityControl != value)
_visibilityControl = value;
OnPropertyChanged("VisibilityControl");
}
And I have the two buttons such as (I'll just post one up);
public ICommand AwakeButtonCommand
{
get
{
return _awakeButtonCommand
?? (_awakeButtonCommand = new Resources.ActionCommand(() =>
{
VisibilityControl = true;
}));
}
}
It doesn't work, obviously. I think what's throwing me is because I want several things changed when one button is pressed, etc. It's throwing me off.
I've not done any Windows Phone development but here's one way of doing it in WPF that might be applicable to WP also.
First, your ViewModel would have a couple of Boolean properties indicating which state is active (one would be a mirror of the other):
public bool IsAwake
{
get
{
return _isAwake;
}
set
{
if (_isAwake != value)
{
_isAwake = value;
// raise PropertyChanged event for *both* IsAwake and IsAsleep
}
}
}
bool _isAwake;
public bool IsAsleep
{
get
{
return !_isAwake;
}
}
Then your View would contain both parts of the UI (asleep & awake) but would switch between the two parts by binding their Visibility property to these Boolean properties of your ViewModel:
<StackPanel>
<StackPanel x:Name="AwakePart"
Visibility="{Binding IsAwake, Converter={StaticResource btvc}}">
... "Going to sleep" button here ...
</StackPanel>
<StackPanel x:Name="AsleepPart"
Visibility="{Binding IsAsleep, Converter={StaticResource btvc}}">
... Elapsed time text block and "I'm awake" button here ...
</StackPanel>
</StackPanel>
You will also need a BooleanToVisibilityConverter instance somewhere in your XAML resources:
<... .Resources>
<BooleanToVisibilityConverter x:Key="btvc" />
</... .Resources>
I've used two Boolean properties in this example as it makes the XAML a little easier, however you could also use a DataTrigger -- assuming they have those in Windows Phone -- in which case you would only need one Boolean property. You would then write a trigger to toggle the Visibility properties of the two parts:
<DataTrigger Binding="{Binding IsAwake}" Value="True">
<Setter TargetName="AwakePart" Property="Visibility" Value="Visible" />
<Setter TargetName="AsleepPart" Property="Visibility" Value="Hidden" />
</DataTrigger>
For this to work you would need to explicitly set the "AwakePart" visibility to Hidden in the XAML to start with and ensure that in your ViewModel the IsAwake property is false by default. You would also need to remove the bindings on the visibility properties (as these would now be set via the trigger).
I have an ObservableCollectiong<StringWrapper> (StringWrapper per this post) named Paragraphs bound to an ItemsControl whose ItemTemplate is just a TextBox bound to StringWrapper.Text.
XAML
<ItemsControl Name="icParagraphs" Grid.Column="1" Grid.Row="7" ItemsSource="{Binding Path=Paragraphs, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<StackPanel Orientation="Vertical">
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Name="tbParagraph" TextWrapping="Wrap" AcceptsReturn="False" Text="{Binding Path=Text}" Grid.Column="0" KeyUp="tbParagraph_KeyUp" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C#
public class StringWrapper
{
public string Text { get; set; }
public StringWrapper()
{
Text = string.Empty;
}
public StringWrapper(string text)
{
Text = text;
}
}
I'm trying to make it so when I press enter in a TextBox, I insert a StringWrapper in my ObservableCollection after the StringWrapper bound to the TextBox that's currently focused, which generates a new TextBox. So far, my code does this, though there are a couple glitches to work out.
My question is, how do I then set the focus to the newly generated TextBox? As far as I can tell, the control generation happens after the function that inserts the string returns.
I looked for something like an ItemsControl.ItemsSourceChanged event, but, at least, that name doesn't exist. I also tried attaching a handler to ObservableCollection.CollectionChanged, but that too seemed to fire before the TextBox was generated. Last, since the ItemsControl.Template is a StackPanel, I looked for a StackPanel.ControlAdded event, but couldn't find that either.
Ideas? Thanks!
You may have to handle CollectionChanged and then schedule the focus action to occur in the future using Dispatcher.BeginInvoke with a priority of Loaded. That should give the ItemsControl an opportunity to generate a container and perform layout.
I need to display hierarchical data like:
public class Element
{
public string Name { get; private set; }
public Element[] Elements { get; private set; }
}
It would be just vertical panel with rectangle (with Name) for each element. If element is clicked, its child elements are displayed below it (element is expanded). If one of them is clicked, its elements appear and so on.
I already googled this and found out that there is no HierarchicalDataTemplate and no treeview in WinRT.
So I started to do it by myself.
I created ItemsControl and DataTemplate DataTemplate1 for it. In DataTemplate1 I also create ItemsControl and set DataTemplate2 as ItemTemplate. In DataTemplate2, ItemTemplate is DataTemplate3 and so on. The last DataTemplate is without ItemsControl.
In buttons Click event I change Elements IsVisible property for any elements in DataModel (that is Element[]), so it is easy to perform any custom logic to expand/collapse elements.
<DataTemplate x:Key="DataTemplate2">
<StackPanel Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
<Button Style="{StaticResource ItemButtonStyle}"
Click="MenuElement_Click">
<TextBlock Style="{StaticResource ItemTextBlockStyle}" Text="{Binding Name}"/>
</Button>
<ItemsControl ItemsSource="{Binding Elements}" ItemTemplate="{StaticResource DataTemplate3}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="DataTemplate1">
<StackPanel Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
<Button Style="{StaticResource ItemButtonStyle}"
Click="MenuElement_Click">
<TextBlock Style="{StaticResource ItemTextBlockStyle}" Text="{Binding Name}"/>
</Button>
<ItemsControl ItemsSource="{Binding Elements}" ItemTemplate="{StaticResource DataTemplate2}"/>
</StackPanel>
</DataTemplate>
It works fine, but the problem is that if I want to enable 10 levels of hierarchy, I have to copypast 10 datatemplates. And 11 level still will not be available.
I also tried to create DataTemplate in C# and manually apply DataTemplate for its ItemSource and so on, in recursive method.
But I found 2 problems.
I don't know actually how to create DataTemplate in metro (C#), because it has no VisualTree property. I can only make (var dt= new Datatemplate();) and I don't know how to change it.
If I read DataTemplate from XAML (var dateTemplateRoot = (DataTemplate)this.Resources["DataTemplate1"];)
I still can't find ItemsControl in it and change its DataTemplate.
Actually, I can use var content = dateTemplateRoot.LoadContent(); and then find ItemsControl by VisualTreeHelper, but I can't use content after that as DataTemplate (content has type DependencyObject).
So, actually I have 2 questions.
Is it a good approach to perform hierarchical dropdown list by "binding" all items and only switch Visibility property?
The second is - how to enable unlimited level of hierarchical nesting?
WinRT XAML Toolkit has a TreeView control now. Check it out: http://winrtxamltoolkit.codeplex.com/SourceControl/changeset/view/b0ee76bd6492#WinRTXamlToolkit/Controls/TreeView/TreeView.cs
Take care though - this is just a rough port from Silverlight Toolkit and might not work so well. Also if you are planning on releasing it as part of a Windows Store application - you would need to heavily restyle it unless your app is desktop-only since it is not very touch-friendly.