Applying style to elements inside a DataTemplate - c#

I have a UserControl which simply contains a TextBlock and TextBox inside a DataTemplate. This is done in the following way:
<UserControl.Resources>
<DataTemplate DataType="{x:Type Binding:StringBindingData}" x:Key="dataTemp">
<StackPanel Orientation="Horizontal" Name="sPanel">
<TextBlock Name="txtDescription" Text="{Binding Description}" />
<TextBox Name="textboxValue" Text="{Binding Mode=TwoWay, Path=Value, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl Name="textItemsControl" ItemsSource="{Binding}"/>
</Grid>
I need to be able to apply different styles to the TextBlock/TextBox under different circumstances. For example in certain instances I would like to be able to apply a white Foreground to the TextBlock or change the width of a TextBox.
I have tried a few different approaches:
In the window where the control is being used I have set the style for the TextBlock:
<Style TargetType="{x:Type TextBlock}" >
<Setter Property="Foreground" Value="White" />
</Style>
This worked for all other TextBlocks in the window.
I also attempted to get the DataTemplate in the codebehind using
var myDataTemplate = (DataTemplate)this.Resources["dataTemp"];
But was unable to get any further in applying the style to all the TextBlock elements.

I am not sure of your requirement though. But for finding controls from code behind, i would recommend to use VisualTreeHelper. I generally used this helper function to do my stuff -
public IEnumerable<T> FindVisualChildren<T>( DependencyObject depObj )
where T : DependencyObject
{
if( depObj != null )
{
for( int i = 0; i < VisualTreeHelper.GetChildrenCount( depObj ); i++ )
{
DependencyObject child = VisualTreeHelper.GetChild( depObj, i );
if( child != null && child is T )
{
yield return (T)child;
}
foreach( T childOfChild in FindVisualChildren<T>( child ) )
{
yield return childOfChild;
}
}
}
}
Usage:
foreach (var textBlock in FindVisualChildren<TextBlock>(this))
{
/* Your code here */
}

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.

create a button programmatically with styles

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");

Assigning zIndex order to image and text elements on a canvas in Silverlight

I have an image editor in silverlight that allows the user to add, manipulate and delete image and text elements on a canvas. I notice it seems to act strangely when adding new elements sometimes and they will be placed behind an existing element for example. Below is the code for adding image elements, and the order elements method which it calls. I inherited this code from someone else so at times I can't follow what his intention was. The code doesn't seem to do what it is supposed to though.
Can someone suggest a better way to assign a Z-Index value to elements that I am adding?
XAML of my workspace canvas -
<Canvas x:Name="pnlCanvas" HorizontalAlignment="Center" VerticalAlignment="Center" Height="{Binding Path=CanvasHeight, Mode=OneWay,UpdateSourceTrigger=Default}"
Width="{Binding Path=CanvasWidth, Mode=OneWay, UpdateSourceTrigger=Default}" >
<ItemsControl ItemsSource="{Binding Path=Elements, Mode=OneWay}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="{Binding Path=CanvasBackground, Mode=OneWay}"
Height="{Binding Path=CanvasHeight, Mode=OneWay,UpdateSourceTrigger=Default}"
Width="{Binding Path=CanvasWidth, Mode=OneWay, UpdateSourceTrigger=Default}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Canvas>
Adding image element -
private void AddImageElement(object param)
{
bool? gotImage;
string fileName;
BitmapImage imageSource = GetImageFromLocalMachine(out gotImage, out fileName);
OrderElements();
if (gotImage == true)
{
Image image = new 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;
}
}
Order Elements -
private void OrderElements()
{
var elList = (from element in this.Elements
orderby element.GetValue(Canvas.ZIndexProperty)
select element).ToList<FrameworkElement>();
for (int i = 0; i < elList.Count; i++)
{
FrameworkElement fe = elList[i];
fe.SetValue(Canvas.ZIndexProperty, i);
}
this.Elements = new ObservableCollection<FrameworkElement>(elList);
}
My end intention once I have this sorted out is to include a layers container like in Photoshop etc. where I will be able to reorder the elements. Hopefully someone can help get me moving in that direction. Basically how do I set the Z-Index correctly because I don't think this is doing it.
An ItemsControl wraps each element in a <ContentPresenter> tag, so although the ZIndex is set on your element, it doesn't get applied because the ZIndex on the ContentPresenter is what matters
What actually gets rendered looks like this:
<Canvas>
<ContentPresenter>
<Image Canvas.ZIndex="0" />
</ContentPresenter>
<ContentPresenter>
<Image Canvas.ZIndex="1" />
</ContentPresenter>
...
</Canvas>
To fix the issue, set the ZIndex in the ItemContainerStyle so it gets applied to the ContentPresenter instead of the UI Element
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.ZIndex" Value="{Binding Canvas.ZIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
For more information, see the bottom section of my blog post about WPF's ItemsControl
Edit:
Apparently Silverlight doesn't have an ItemsContainerStyle for the ItemsControl.
In that case, simply set an implicit style for the ContentPresenter that sets the Canvas.ZIndex value in your ItemsControl.Resources
<ItemsControl.Resources>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.ZIndex" Value="{Binding Canvas.ZIndex}" />
</Style>
</ItemsControl.Resources>

Control Enabled/Disabled behaviour in Silverlight

I have an application that has a lot of textboxes in it. Also this textboxes actually are never disabled, but rather become ReadOnly instead. I prefer using one property of ContentControl for all my controls, but if i set IsEnabled to false all my textboxes become disabled. How can i make them go to readonly mode? I prefer not making my own control though, maybe i can use styles or something to reassign behaviour?
Edit: I actually am looking for solution that allows me to use binding to bind state of all controls(IsReadOnly) through binding in one place. Like:
<ContentControl IsEnabled="{Binding boolFlag, Mode=OneWay}">
<Grid x:Name="LayoutRoot" HorizontalAlignment="Stretch">
....
<TextBox/>
</Grid>
</ContentControl>
It seems like the use of the DataForm control would be best in your case. It would allow you to control every field within it as a group. It does provide the IsReadOnly option plus it comes with many free features that are very nice.
This video gives a good introduction
http://www.silverlight.net/learn/data-networking/data-controls/dataform-control
http://www.silverlightshow.net/items/Creating-Rich-Data-Forms-in-Silverlight-3-Introduction.aspx
http://www.silverlight.net/content/samples/sl4/toolkitcontrolsamples/run/default.html
Look for dataform
Cheers,
I suggest you to use a simple extension method for your ContentControl that will make textBoxes IsReadOnly = True. For example:
public static class ContentControlEx
{
public static void DisableTextBoxes(this ContentControl contentControl)
{
FrameworkElement p = contentControl as FrameworkElement;
var ts = p.GetChildren<TextBox>();
ts.ForEach(a => { if (!a.IsReadOnly) a.IsReadOnly = true; });
}
public static List<T> GetChildren<T>(this UIElement parent) where T : UIElement
{
List<T> list = new List<T>();
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++) {
UIElement child = VisualTreeHelper.GetChild(parent, i) as UIElement;
if (child != null) {
if (child is T)
list.Add(child as T);
List<T> l1 = GetChildren<T>(child);
foreach (T u in l1)
list.Add(u);
}
}
return list;
}
}
usage (for ContentControl with Name = "content"):
content.DisableTextBoxes();
I have a XAML like this:
<Grid x:Name="LayoutRoot" Background="White">
<ContentControl IsEnabled="True" Name="content">
<StackPanel Margin="15">
<TextBox Width="150" Name="tb1" Margin="5" Text="{Binding tb1}" />
<TextBox Width="150" Name="tb2" Margin="5" Text="{Binding tb2}" />
<TextBox Width="150" Name="tb3" Margin="5" Text="{Binding tb3}"/>
<TextBox Width="150" Name="tb4" Margin="5" Text="{Binding tb4}"/>
<Button Name="bSubmit" Click="bSubmit_Click">Make Textboxes readonly</Button>
</StackPanel>
</ContentControl>
</Grid>
Let me know if it helps...
If I understand right, you want to bind each TextBox.IsReadOnlyProperty to a single bool.
You might try something like this, in a similar fashion to how you bound the IsEnabled property of the ContentControl:
<TextBox IsReadOnly="{Binding boolFlag, Mode=OneWay}" ... /> <!-- in each of your textboxes -->
This should provide you what you need: change boolFlag, every textbox turns on or off.

Make select controls visible based on selected tab using xaml

I've got following code:
private Dictionary<int, UserControl> tabControls = new Dictionary<int, UserControl>();
public MainWindow()
{
InitializeComponent();
tabControls[0] = new Panel1();
tabControls[1] = new Panel2();
tabControls[2] = new Panel3();
tabControls[3] = new Panel4();
tabControls[4] = new Panel5();
tabControls[5] = new Panel6();
tabControls[6] = new Panel7();
tabControls[7] = new Panel8();
}
public object SelectedTab
{
//this is assigned from xaml binding
set
{
OnCurrentTabChanged(tabControl.SelectedIndex);
}
}
void OnCurrentTabChanged(int tabIndex)
{
if (dataDisplay != null)
{
dataDisplay.Children.Clear();
dataDisplay.Children.Add(tabControls[tabIndex]);
}
}
Every time the user selects different tab, an other control appears.
Is there any way to simplify this using xaml?
I cannot put the controls themselves inside the tab control
I've done this before with another TabControl which has it's headers and frame hidden. Then I just bind the SelectedIndex to your other tab's SelectedIndex, and the two are synchronized
<!-- TabControl without the TabHeaders -->
<Style x:Key="TabControl_NoHeadersStyle" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<DockPanel>
<!-- This is needed to draw TabControls with Bound items -->
<StackPanel IsItemsHost="True" Height="0" Width="0" />
<ContentPresenter x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then you can setup your two tab controls, each bound to different sources, and bind the SelectedIndex of one to the SelectedIndex of the other
<TabControl x:Name="MainTabControl" />
<TabControl ItemsSource="{Binding Panels}"
SelectedIndex="{Binding ElementName=MainTabControl, Path=SelectedIndex}"
Style="{StaticResource TabControl_NoHeadersStyle}" />
Another alternative is to bind the SelectedIndex to something in your code-behind, then anytime it changes, raise a PropertyChanged notification on another property that exposes the panel you want to display.
<TabControl SelectedIndex="{Binding SelectedTabIndex} />
<ContentControl Content="{Binding SelectedPanel}" />
and in the code behind
public int SelectedTabIndex
{
get { return _selectedTabIndex;}
set
{
if (_selectedTabIndex != value)
{
_selectedTabIndex = value;
RaisePropertyChanged("SelectedTabIndex");
RaisePropertyChanged("SelectedPanel");
}
}
}
public UserControl SelectedPanel
{
get { return tabControls[SelectedTabIndex]; }
}
TabItem has an IsSelected propery you could bind to that I think would simplify the syntax.
public bool TabIsSelected
{
get { return tabIsSelected; }
set
{
if (value && dataDisplay != null)
{
dataDisplay.Children.Clear();
dataDisplay.Children.Add(tabControls[tabIndex]);
}
tabIsSelected = value;
}
But I still don't get why you can't just put the control in the tabitem?
using codebehind
void OnCurrentTabChanged(int tabIndex)
{
if (dataDisplay != null)
{
UIElemnt[] pp = dataDisplay.Children.Cast<UIElement>().ToArray();
Array.ForEach(pp, x=> x.visibility = Visibility.Collapsed);
pp[tabIndex].visibility = Visibility.Visible;
}
}

Categories