create a button programmatically with styles - c#

Simple syntax question. Programming silverlight 4 on VS2010. I created a button style in xaml:
<UserControl.Resources>
<Style x:Key ="TestbuttonStyle" TargetType="Button">
<Setter Property="Width" Value="150"></Setter>
<Setter Property="Margin" Value="0,0,0,10"></Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Image Source="http://i40.tinypic.com/j5k1kw.jpg" Height="20" Width="20" Margin="-30,0,0,0"></Image>
<TextBlock Text="sampleuser
sample number" Margin="5,0,0,0"></TextBlock>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
I need to create a button in the code behind, but using this style. I tried doign something like this:
Button btn = new Button();
//btn.Style = {TestbuttonStyle}; -what do i put here?
grid.children.add(btn);
how to I apply the style and add to my usercontrol grid?

Initially I thought you were working with WPF. Then I realized it's about Silverlight which doesn't have a hierarchical resource look-up helper method similar to WPF's FindResource or TryFindResource, respectively.
However, a quick search on the internet gave up this article which describes a nice extension method you can use:
public static object TryFindResource(this FrameworkElement element, object resourceKey)
{
var currentElement = element;
while (currentElement != null)
{
var resource = currentElement.Resources[resourceKey];
if (resource != null)
{
return resource;
}
currentElement = currentElement.Parent as FrameworkElement;
}
return Application.Current.Resources[resourceKey];
}
Then you can use it like this:
btn.Style = (Style)this.TryFindResource("TestbuttonStyle");

Related

TreeViewItem styles not being applied when selecting an item dynamically over via input

I have a treeview dynamically generated within the program. It uses properties on the class to select items by default if the user sets the preference for it:
However, when I do this, it applies the default style, rather than the current style, which is currently set and applies a AdonisUI dark mode style if requested, or light if not.
The Tree View (and Style) code:
<Window.Resources>
<Color x:Key="TitleBarColor">#FF191970</Color>
<Color x:Key="TitleBarForeColor">#FFFFFAF0</Color>
<Style x:Key="SystemTreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</Window.Resources>
....
<TreeView Name="tvwSystemTree" Grid.Column="0" Grid.Row="0"
SelectedItemChanged="tvwSystemTree_SelectedItemChanged" ItemContainerStyle="{StaticResource SystemTreeViewItemStyle}"
Visibility="Hidden">
<TreeView.Style>
<Style TargetType="TreeView" BasedOn="{StaticResource {x:Type TreeView}}"/>
</TreeView.Style>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type lAWObjects:SystemObject}" ItemsSource="{Binding Items}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding IconUri, Mode=OneWay}" Height="16" Width="16" />
<TextBlock Text="{Binding Title}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And in code-behind:
ObservableCollection<SystemObject> AstralObjects = new();
SystemObject root = new SystemObject() { Title = ourSystem.SystemName, IconUri = new Uri(SystemObject.SystemLogo) };
foreach(Star v in ourSystem.SystemStars)
{
SystemObject child = new SystemObject() { Title = v.Name, IconUri = new Uri(SystemObject.SunLogo) };
foreach (IOrbitalBody p in ourSystem.PlanetaryBodies)
{
if (p.Parent == v)
{
SystemObject child2 = new SystemObject() { Title = p.Name, IconUri = new Uri(SystemObject.PlanetLogo) };
child.Items.Add(child2);
}
}
root.Items.Add(child);
}
tvwSystemTree.ItemsSource = AstralObjects;
tvwSystemTree.Visibility= Visibility.Visible;
grdDetailView.Visibility = Visibility.Visible;
if (preferences.AutoDisplaySystem)
{
foreach (var v in AstralObjects)
{
if (v.Title == ourSystem.SystemName)
{
v.IsSelected = v.IsExpanded = true;
tvwSystemTree_SelectedItemChanged(this, new RoutedPropertyChangedEventArgs<object>(null, v));
}
}
}
For completion's sake, the SystemObject code that is probably most relevant is that it implements INotifyPropertyChanged. But I can provide it as well if requested.
When this code fires, it applies the normal blue-background and white-text. But if you click any option in the tree, it then applies the style specified colors.
I've tried specifiying that <Style x:Key="SystemTreeViewItemStyle" TargetType="{StaticResource {x:Type TreeViewItem}}"> but it appears AdonisUI doesn't support those properties on it. (And a code-search on github also appears to verify this.)
My only guess is that somehow the selection style is only applied on user interaction. Is there a way around this that I haven't figured out? I'm rather reluctant to apply explicit style colors so I don't have to create variations for any style I may apply in the future.
Update After some investigation I've found out it's because it's overriding Adonis's code (which makes sense) even if I attempt to apply it via {StaticResource {x:Key TreeViewItem}}, but is not respecting any changes I attempt to make via specified dynamic resource.

How to access ListViewItems when DataType is set in DataTemplate of ListView?

I'm working on a UWP app. I want to iterate through all the ListViewItems of a ListView in a page. Here's the xaml for the ListView.
<ListView x:Name="DownloadTaskListView"
ItemsSource="{x:Bind ViewModel.CompletedDownloads}"
HorizontalContentAlignment="Stretch"
Background="{x:Null}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:DownloadTask">
<Grid x:Name="ItemViewGrid" Background="{x:Null}" Margin="4,0,0,0">
....
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
I use this piece of code to achieve this.
foreach(ListViewItem item in DownloadTaskListView.Items)
{
// Do something useful
}
But it gave me an Exception. Because I set the DataType of DataTemplate so runtime throw me an exception that it cannot convert from from DownloadTask (In this case the data type) to the ListViewItem. So I want to ask what is the other way of accessing ListViewItems?
You can use ItemsControl.ContainerFromItem method to find container corresponding to the specified item, then get the root element of this container, in your case it's a Grid. For example like this:
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
foreach (var item in DownloadTaskListView.Items)
{
var listviewitem = item as DownloadTask;
var container = DownloadTaskListView.ContainerFromItem(listviewitem) as ListViewItem;
var ItemViewGrid = container.ContentTemplateRoot as Grid;
//TODO:
}
}
Just be aware that if you want to use this method in SelectionChanged event of your listview, you can just pass the selected Item into ContainerFromItem method, otherwise it will not find the ListBoxItem.
I should say, if it is possible, using data binding is better.
Since you are setting ItemsSource as ViewModel.CompletedDownloads do the Item loop on the same.
foreach(var Items in ViewModel.CompletedDownloads)
{
//Do Something Useful.
}

TextBlock Style to always use Run Tag

In WPF Arabic Mode (FlowDirection="RightToLeft").
When i give a number like -24.7% it will print this as %24.7-
Following code will fix the above mentioned issues.
<Window.Resources>
<Style TargetType="Run">
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
</Window.Resources>
<Grid FlowDirection="RightToLeft" >
<Grid HorizontalAlignment="Left" Margin="114,127,0,0" VerticalAlignment="Top" Width="279" Height="97">
<TextBlock x:Name="textBlock" Text="-24.7%" ><Run></Run></TextBlock>
</Grid>
</Grid>
Now i want to put the <run><run> tag to all of my Text Blocks Contents, How can i achieve this, So i don't have to replace all of my TextBlocks in the code.
How to do this by creating a Style...??
note: I can't go to the TextAlign=Right solution as i can't edit all the textblockes in the application
Can't say I like your approach, but I don't know Arabic gotchas and your situation, so won't argue about that. You can achieve what you want using attached properties (or blend behaviors). Like this:
public static class StrangeAttachedProperty {
public static bool GetAddRunByDefault(DependencyObject obj) {
return (bool) obj.GetValue(AddRunByDefaultProperty);
}
public static void SetAddRunByDefault(DependencyObject obj, bool value) {
obj.SetValue(AddRunByDefaultProperty, value);
}
public static readonly DependencyProperty AddRunByDefaultProperty =
DependencyProperty.RegisterAttached("AddRunByDefault", typeof (bool), typeof (StrangeAttachedProperty), new PropertyMetadata(AddRunByDefaultChanged));
private static void AddRunByDefaultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var element = d as TextBlock;
if (element != null) {
// here is the main point - you can do whatever with your textblock here
// for example you can check some conditions and not add runs in some cases
element.Inlines.Add(new Run());
}
}
}
And in your resources set this property for all text blocks:
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="local:StrangeAttachedProperty.AddRunByDefault" Value="True" />
</Style>
<Style TargetType="Run">
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
</Window.Resources>

How can I find a style trigger-embedded element by name in WPF?

First, the heart of the question: If an element is assigned as the Content of a ContentControl via a style trigger, I can't seem to find it by name.
Now, for more detail: I have a panel that varies greatly in its layout and functionality based on its data context, which is a bug from a bug depot. When that bug is null, it is a search form, when it is non-null, it is a simple viewer for properties of that bug. The XAML then look something like:
<ContentControl DataContext="...">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content">
<Setter.Value>
...
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Content">
<StackPanel>
<TextBox Name="Waldo"/>
<Button .../>
</StackPanel>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
When the user clicks the button that sits alongside the text box, I get a callback in the code behind. From that point I'd like to be able to access various properties of the text box. The question is, where's Waldo? :)
In the code behind I have tried a few variants of the following, all with little success:
this.FindName("Waldo"); // Always returns null
I've seen a lot of discussion on this topic as it relates to templates but not as it relates to setting content directly with triggers. Maybe it's because I am violating all sorts of best practices by doing this :)
Thank you!
If an element is assigned as the Content of a ContentControl via a style trigger, I can't seem to find it by name.
If you needed to access to the Content before trigger occurs, it would most likely not possible. In this situation, the main thing to get access after the DataTrigger occurs.
I am violating all sorts of best practices by doing this
Maybe it's not the right way to work with the Сontrol in WPF, the more that you still need access to dynamic content, which can later be changed. But in any case, there are two ways to work with the Сontrol - it's like now and in the MVVM style. MVVM style is best suited for large and less complex applications with different business logic. If in your case for easy application, in this situation, I do not see anything wrong with that. In addition to doing a project in MVVM style need from scratch, combine conventional method and the correct method is not a good way.
I created a small example to demonstrate access controls for a given situation. There is a property that corresponds to the type of Content, the default is Init. If you assigns null for this property, the dynamic Content is loaded.
That's how I get access to TextBox:
private void GetAccessToTextBox_Click(object sender, RoutedEventArgs e)
{
TextBox MyTextBox = null;
StackPanel panel = MainContentControl.Content as StackPanel;
foreach (object child in panel.Children)
{
if (child is TextBox)
{
MyTextBox = child as TextBox;
}
}
if (MyTextBox != null)
{
MyTextBox.Background = Brushes.Gainsboro;
MyTextBox.Height = 100;
MyTextBox.Text = "Got access to me!";
}
}
Below it's a full example:
XAML
<Window x:Class="AccessToElementInContentControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:AccessToElementInContentControl"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<this:TestData />
</Window.DataContext>
<Window.Resources>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Content">
<Setter.Value>
<Label Content="InitContent"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TypeContent}" Value="{x:Null}">
<Setter Property="Content">
<Setter.Value>
<StackPanel Name="NullStackPanel">
<TextBox Name="Waldo" Text="DynamicText" />
<Button Width="100" Height="30" Content="DynamicButton" />
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ContentControl Name="MainContentControl" />
<Button Name="SetContentType"
Width="100"
Height="30"
HorizontalAlignment="Left"
Content="SetContentType"
Click="SetContentType_Click" />
<Button Name="GetAccessToButton"
Width="110"
Height="30"
HorizontalAlignment="Right"
Content="GetAccessToTextBox"
Click="GetAccessToTextBox_Click" />
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void SetContentType_Click(object sender, RoutedEventArgs e)
{
TestData test = this.DataContext as TestData;
test.TypeContent = null;
}
private void GetAccessToTextBox_Click(object sender, RoutedEventArgs e)
{
TextBox MyTextBox = null;
StackPanel panel = MainContentControl.Content as StackPanel;
foreach (object child in panel.Children)
{
if (child is TextBox)
{
MyTextBox = child as TextBox;
}
}
if (MyTextBox != null)
{
MyTextBox.Background = Brushes.Gainsboro;
MyTextBox.Height = 100;
MyTextBox.Text = "Got access to me!";
}
}
}
public class TestData : NotificationObject
{
private string _typeContent = "Init";
public string TypeContent
{
get
{
return _typeContent;
}
set
{
_typeContent = value;
NotifyPropertyChanged("TypeContent");
}
}
}
public class NotificationObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Add Items to ListBox and Style

I have a simple class:
public class Foo
{
public string Text { get; set; }
public bool AppleStyle { get; set; }
public Foo(string text, bool applyStyle)
{
Text = text;
ApplyStyle = applyStyle;
}
public override string ToString()
{
return Text;
}
}
Which is then used to add items to a ListBox:
var one = new Foo("Some Text", false);
var two = new Foo("More Text", true);
MyListBox.Items.Add(one);
MyListBox.Items.Add(two);
I then loop through the items in the ListBox to figure out how to style them. This is where I get stuck. I tried inheriting from ListBoxItem for the class, but no items get added if I do that.
for (int i = 0; i < MyListBox.Items.Count; i++)
{
if(((Foo)MyListBox.Items[i]).ApplyStyle)
{
((ListBoxItem)MyListBox.Items[i]).Style = Resources["MyStyle"] as Style;
}
}
Update:
In MainWindow.xaml:
<Window.Resources>
<Style x:Key="MyStyle" TargetType="ListBoxItem">
<Setter Property="Background" Value="Bisque"></Setter>
<Setter Property="FontWeight" Value="Bold"></Setter>
</Style>
</Window.Resources>
Update 3:
Making some progress, just need to know how to refresh the styles (after clicking on a button). Plus if Resource is not in MainWindow.xaml, would it then look in App.xaml before returning null?
MainWindow.xaml
<Window...>
<Window.Resources>
<Style x:Key="MyClass" TargetType="ListBoxItem">
<Setter Property="Background" Value="Bisque"></Setter>
<Setter Property="FontWeight" Value="Bold"></Setter>
</Style>
<myapp:MyListItemStyleSelector x:Key="MyListItemStyleSelector" />
</Window.Resources>
<Grid>
...
<ListBox .... ItemContainerStyleSelector="{StaticResource: MyListItemStyleSelector}" />
...
</Grid>
</Window>
MyListItemStyleSelector.cs
public class MyListItemStyleSelector : StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(container);
int index = ic.ItemContainerGenerator.IndexFromContainer(container);
Style applyStyle = null;
var data = item as Foo;
if (data != null && data.ApplyStyle)
{
applyStyle = ic.TryFindResource("MyStyle") as Style;
}
return applyStyle;
}
}
I think you have some sort of mixup here, i try to explain as good as i can.
First of all You usually never need to change the Style in code, like your last code block.
One thing that is difficult to understand in the beginning is the use of a ItemContainerStyle and DataTemplate.
I would suggest that you do the following.
Instead of changing the style off your ListBoxItem see if it is sufficient to use a DataTemplate. The DataTemplate defines how the Content of your ListBoxItem is shown.
<DataTemplate TargetType="{x:Type Foo}">
<!-- your visuals and controls here -->
</DataTemplate>
Now if you want to use different datatemplates you could use different classes and create different DataTemplates for them, or you use a DataTemplateSelector
public class FooTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
var mdl = item as Foo;
if( mdl.AppleStyle )
return element.FindResource("appleTemplate") as DataTemplate;
return element.FindResource("normalTemplate") as DataTemplate;
}
}
Create that templateselector in xaml and reference it in your listbox
<myNs:FooTemplateSelector x:Key="fooTemplateSelector"/>
<Listbox DataTemplateSelector="{StaticResource fooTemplateSelector}"/>
now you need to create 2 DataTemplates appleTemplate *normalTemplate* and you can easyl distinguish which data template to use vial the selector. Which is done automatically in the ListBox for you.
If you really want to change the Style of the ItemContainer you can use ItemContainerStyleSelector which works similar to the DataTemplateSelector. But i would not suggest it. You should supply the content and leave the ListBoxItem as it is, only if you want to modify the design(in this case, the selection color etc.), otherwise it might confuse the user or break functionality.
If you add data-objects directly to the ListBox the container-items will be generated automatically, you cannot get them this way.
Use the ItemContainerGenerator:
((ListBoxItem)MyListBox.ItemContainerGenerator.ContainerFromIndex(i)).Style = Resources["MyStyle"] as Style;
Why not do this in the XAML?
<ListBox Name="MyListBox">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding ApplyStyle}" Value="True">
<Setter Property="Background" Value="Bisque" />
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
But your overall problem is that ListBox.Items returns a collection of data objects, not XAML Controls. To get the XAML control that contains the Data Object you have to do as H.B. suggested and use MyListBox.ItemContainerGenerator.ContainerFromItem(dataObject) to get the XAML Container for the data object. Just be sure you wait until after the ItemContainerGenerator has finished rendering items to get the container (I believe it has a Status property or StatusChanged event you can use... it's been a while and I can't remember the exact syntax)

Categories