I trying to create new custom control named PlaceHolderTextBox inherited from TextBox. So I added a TextBlock directly above the TextBox using Template :
<!-- PlaceHolderTextBox template -->
<ControlTemplate TargetType="TextBox">
<Grid>
<TextBlock x:Name=""holder"" />
<ScrollViewer x:Name=""PART_ContentHost"" Margin=""0"" />
</Grid>
</ControlTemplate>
All are ok for now, the real TextBox will appear in the ScrollViewer automatically. Now I want to derive this class to create my AutocompleteTextBox control inherited from PlaceHolderTextBox, so I have to edit template again, but how can I add the base control PlaceHolderTextBox to the template like I did by ScrollViewer above? !!
<!-- AutocompleteTextBox template -->
<Grid>
<PlaceHolderTextBox Name=""tbTextBox""/>
<Popup x:Name=""pop""
Visibility=""Visible""
StaysOpen=""False""
Placement=""Bottom"">
<ListBox x:Name=""lbSuggestions""
Height=""135""
/>
</Popup>
</Grid>
The problem in the PlaceHolderTextBox control shown in the template, it will be treated as new control (not bound to the inherited properties like Text) and not like the ScrollViewer in the first class.
I tried to use PART_XXX mechanism in the base class to solve this issue, but I failed to edit incoming template -like ScrollViewer did- before use :
Public Class PlaceHolderTextBox
Inherits TextBox
Public Overrides Sub OnApplyTemplate()
Dim host = Me.GetTemplateChild("PART_PlaceHolderContentHost")
If host IsNot Nothing AndAlso TypeOf host Is Grid Then
Dim xaml = "<Grid>
<TextBlock x:Name=""holder"" />
<Grid x:Name=""PART_ContentHost"" Margin=""0"" />
</Grid>"
Dim strReader = New StringReader(xaml)
Dim xmlReader = Xml.XmlReader.Create(strReader)
Dim grid As Grid = XamlReader.Load(xmlReader)
CType(host, Grid).Children.Add(grid)
End If
MyBase.OnApplyTemplate()
End Sub
End Class
So I added new controls to the grid (PART_PlaceHolderContentHost) but ScrollViewer here will not work correctly because it added to the real grid (not template). So how can I edit incoming template to make ScrollViewer treated as TextBoxBase.
Related
I am very new to WPF and just would to ask your help for a very basic method of getting the Windows controls and their children as in Winform app. Bottom line is to have reusable code for multiple window/pages in a different different class.
Bunches of thanks before.
Public Sub GetControl(Wn As Window)
For Each Ctrl As Control In Wn.Controls
'Code here
If Ctrl.HasChildren = True Then
'Code here
End If
Next
End Sub
So here's the down low. You need to look for UIElement, which is a base class for all UIElements in XAML. There are two main types that host controls. ContentControl and Panel.
A ContentControl has a 'Content' property that is potentially containing an object.
A Panel has a collection of UIElements property 'Children' and of type UIElement.
If you're looking just for the elements of a Window or ANY UIElement then you need to recursively search and make that list based on that information.
A Window inherits from ContentControl but that Content might be a Grid or StackPanel or any Panel or sorts and may have Children of UIElement also.
You cycle through them all until you get the results.
public MainWindow()
{
InitializeComponent();
foreach (var element in GetAllElementsFrom(this))
Debug.WriteLine(element.ToString());
}
private IEnumerable<UIElement> GetAllElementsFrom(UIElement element)
{
var uiElements = GetSingleElement();
switch (element)
{
case Panel panel:
foreach (var child in panel.Children)
uiElements = uiElements.Concat(GetInnerElements(child));
break;
case UserControl userControl:
uiElements = uiElements.Concat(GetInnerElements(userControl.Content));
break;
case ContentControl contentControl:
if (contentControl.Content is UIElement uiElement)
uiElements = uiElements.Concat(GetInnerElements(uiElement));
break;
}
return uiElements;
IEnumerable<UIElement> GetSingleElement()
{
yield return element;
}
}
Here's the XAML I used.
<Grid>
<Button>
<DockPanel>
<ContentControl>
<Grid>
<TextBox />
</Grid>
</ContentControl>
</DockPanel>
</Button>
<StackPanel>
<Label />
<TextBlock>
Hey There!!
</TextBlock>
<Grid>
<Ellipse />
</Grid>
</StackPanel>
</Grid>
And here's the result I got in my debug window:
System.Windows.Controls.Grid
System.Windows.Controls.Button
System.Windows.Controls.DockPanel
System.Windows.Controls.ContentControl
System.Windows.Controls.Grid
System.Windows.Controls.TextBox
System.Windows.Controls.StackPanel
System.Windows.Controls.Label
System.Windows.Controls.TextBlock
System.Windows.Controls.Grid
System.Windows.Shapes.Ellipse
Happy Coding! Note: Uses C# 7 syntax; if you're not on C# 7 then just make the changes, I think they're straight forward.
Situation:
I have a "wrapper panel" UserControl like this (namespaces and visual details removed for brevity):
<UserControl ...>
<Grid x:Name="LayoutRoot" Background="White">
<ContentPresenter x:Name="integratedPanelContent" Margin="5" />
</Grid>
</UserControl>
Then in the Code-behind I have registered a dependency property
public FrameworkElement PanelContent
{
get { return (FrameworkElement)GetValue(PanelContentProperty); }
set { SetValue(PanelContentProperty, value); }
}
public static readonly DependencyProperty PanelContentProperty =
DependencyProperty.Register("PanelContent", typeof(FrameworkElement), typeof(MyWrapperPanel),
new PropertyMetadata(null, OnPanelContentChanged));
private static void OnPanelContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyWrapperPanel)d).OnSetContentChanged(e);
}
protected virtual void OnSetContentChanged(DependencyPropertyChangedEventArgs e)
{
if (PanelContent != null)
integratedPanelContent.Content = PanelContent;
}
Now I can wrap any content into my control:
<my:MyWrapperPanel x:Name="myWrap">
<my:MyWrapperPanel.PanelContent>
<TextBlock x:Name="tbxNothing" Text="Nothing" />
</my:MyWrapperPanel.PanelContent>
</my:MyWrapperPanel>
Description of the problem:
Whenever I try to use the reference tbxNothing in codebehind, the system throws NullReferenceException because tbxNothing, although as a reference exists, does not point to the TextBlock defined in XAML, but is null.
Possible (but inconvenient) workaround:
There is a workaround where I remove x:Name from the TextBlock, and then I explicitely define private TextBlock called tbxNothing. Then in the OnNavigatedTo event handler I assign the value the following way:
tbxNothing = myWrap.PanelContent as TextBlock;
This works but is not a right way to do it, because if a content is a stackpanel that contains wanted controls, I'd have to traverse the tree to find what I need, which is extremely inconvenient.
Question:
Why is the textblock no longer visible when wrapped in a User control (the way described), and how to get it by its x:Name in code-behind?
The problem is your panel content is falling between two stools. On the one hand the content with the name "tbxNothing" is create in the namescope of the main page. However its not added to the object tree at that point. On the other hand the MyWrapperPanel being a UserControl has its own namescope and its into the object tree below this that the item with then name "tbxNothing" is added. FindName on the main page won't find anything inside the MyWrapperPanel because it has its own namescope and FindName on the MyWrapperPanel won't find "tbxNothing" because it doesn't exist in its namescope (being actually created in the main page).
The answer is don't use a UserControl as a basis for MyWrapperPanel. Instead create a Silverlight Template Control. Modify the base class it inherits from to ContentControl and tweak its default template to include a ContentPresenter. Should look something like this:-
public class MyWrapperPanel : ContentControl
{
public MyWrapperPanel ()
{
this.DefaultStyleKey = typeof(MyWrapperPanel );
}
}
then in themes/generic.xaml the style can look like this:-
<Style TargetType="local:MyWrapperPanel">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyWrapperPanel">
<Grid>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Your main page xaml would look like:-
<my:MyWrapperPanel x:Name="myWrap">
<TextBlock x:Name="tbxNothing" Text="Nothing" />
</my:MyWrapperPanel>
Note that deriving from ContentControl gives you a Content property which the ContentPresenter auto-magically wires to.
I'm looking to set a UserControl to be the Content of another UserControl in XAML, in the same way you can set a Button's Content to be anything.
Let's say my "outer" UserControl looks like this:
<MyUserControl>
<Grid>
<Border FancyPantsStyling="True">
<-- I want to insert other controls here -->
</Border>
</Grid>
</MyUserControl>
And I'd like to instantiate this way:
<local:MyUserControl>
<local:MyUserControl.Content>
<local:AnotherControl />
</local:MyUserControl.Content>
</local:MyUserControl>
How do I design MyUserControl to render it's Content in a specific location?
All the stuff you put into your UserControl's XAML is its Content so you can't inject something else by setting the Content property. There are a few different ways you could handle this. If you don't have anything in the code-behind for MyUserControl you can just get rid of it and use something like:
<ContentControl>
<ContentControl.Template>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid>
<Border FancyPantsStyling="True">
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</ContentControl.Template>
<local:AnotherControl/>
</ContentControl>
If you have code behind that doesn't access the XAML elements directly you can do a similar thing with your existing control (since UC derives from ContentControl):
<local:MyUserControl>
<local:MyUserControl.Template>
<ControlTemplate TargetType="{x:Type local:MyUserControl}">
<Grid>
<Border FancyPantsStyling="True">
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</local:MyUserControl.Template>
</local:MyUserControl>
If you need to keep the existing content connected to your code-behind you can use a DataTemplate to pass in the external content (into a new DP on MyUserControl) and apply that template to a ContentControl in the UC's XAML.
I got an idea, then tried it and it worked for me. I just wanted to share this to other people. I hope it will be useful.
The video link which explains what is the end of the solution: Video Link
The basic idea is to create UIElement DependencyProperty instead of creating Border DependencyProperty
Firstly, you should add your borders or panels or whatever you want to your user control (in your case it'S "MyUserControl") and make sure it has a name to access from .cs file:
<Border x:Name="LeftBorder" Grid.Column="0">
Then you should add a public UIElement value to your user control (in your case it's "MyUserControl"):
public UIElement LeftBorderChild
{
get { return (UIElement)GetValue(LeftBorderChildProperty ); }
set { SetValue(LeftBorderChildProperty , value); }
}
Secondly, type of your Dependencyproperty must be UIElement:
public static readonly DependencyProperty LeftBorderChildProperty = DependencyProperty.Register("LeftBorderChild", typeof(UIElement), typeof(MyUserControl), new PropertyMetadata(new PropertyChangedCallback(LeftBorderChildChanged)));
After these, typing events:
public static void LeftBorderChildChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyUserControl thisUserControl = d as MyUserControl;
thisCombobox._LeftBorderChildChanged(e); // Calling local event. The new child will be added in this local event function.
}
public void _LeftBorderChildChanged(DependencyPropertyChangedEventArgs e)
{
// In this function, new child element will be added to inside of LeftBorder
this.LeftBorder.Child = (UIElement)e.NewValue; // Sets left border child
}
We're done with this class. Let's call it from other class and add a control inside of it.
<local:MyUserControl Width="312" HorizontalAlignment="Right"
Margin="48, 0, 0, 0" VerticalAlignment="Center"
Height="56" >
<local:MyUserControl.LeftBorder>
<-- You can insert another control here -->
<-- Just don't remember that if you want to add more than one controls, you should add a panel then add controls into inside of the panel because Border child can only 1 child item -->
<StackPanel>
<-- Now you can insert your controls -->
</StackPanel>
</local:MyUserControl.LeftBorder>
</local:MyUserControl>
Note: When you do this firstly, you have to run your program before viewing in xaml designer. After running your program, all design systems are going to run synchronously.
I hope i understood what you mean and answered correctly.
Thank You
unless i misunderstood the question, you can use in your control and set its content to whatever you need.
Is there any way to have a listbox populated by both static and dynamic items?
I am writing a windows phone 7 app and would like to have one static listboxItem at the top or bottom and then bind other items from the viewModel. I tried setting both a static listboxItem and then also a dataTemplate but the static item is replaced by the dynamic items.
Edit:
I have found several posts that show how to create a custom control that inherits from listbox which allows multiple templates. How might I create a custom control which adds a section for static items which are always present regardless of binding.
If you are trying to do MVVM and are also two-way binding the SelectedItem of the ListBox, it is going to be much easier/cleaner to just bind one collection to the ItemsSource property.
Can you just pre-populate the collection in your ViewModel with the static item? You could then merge your dynamic items into the already existing collection when they are available (coming back from a web service or whatever). It seems like you would want this kind of logic in your ViewModel anyway, and just expose a single list to the View to use with the ListBox.
Because there are two different types of items, I think your best bet would be to create a custom ListBox subclass which adds a new DependencyProperty to allow you to bind and display a second list. This would also require a new default style to display the second list appropriately in the same ScrollViewer as the normal <ItemsPresenter/>.
Here is an example of my custom ListBox to allow this:
public class MyListBox : ListBox
{
public MyListBox()
: base()
{
this.DefaultStyleKey = typeof(MyListBox);
}
public static readonly DependencyProperty StaticItemsProperty = DependencyProperty.Register(
"StaticItems",
typeof(IList),
typeof(MyListBox),
null);
public IList StaticItems
{
get { return (IList)GetValue(StaticItemsProperty); }
set { SetValue(StaticItemsProperty, value); }
}
}
You would then have to copy the entire default ListBox style into your themes/generic.xaml resource dictionary and modify it to become the default style for the MyListBox control. The only thing I modified from the default style (aside from the TargetType attribute) was the content of the ScrollViewer which had the original list:
<Style TargetType="custom:MyListBox">
<!-- all the same old XAML for the normal ListBox -->
<ScrollViewer x:Name="ScrollViewer" Background="{TemplateBinding Background}" BorderBrush="Transparent" BorderThickness="0" Padding="{TemplateBinding Padding}" TabNavigation="{TemplateBinding TabNavigation}">
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{TemplateBinding StaticItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsPresenter/>
</StackPanel>
</ScrollViewer>
<!-- rest of the same old ListBox XAML -->
</Style>
As you can see I modified the ScrollViewer which normally just contained the ItemsPresenter for the ListBox and replaced it with a StackPanel containing a new ItemsControl bound to the new StaticItems DependencyProperty I added to MyListBox. I modified the DataTemplate for this ItemsControl to show a TextBox. The normal ItemsPresenter with the normal ItemsTemplate would then show up below the static list in the ScrollViewer.
This custom ListBox can then be used in place of a normal ListBox to bind to two different lists in your ViewModel, both to your static items and your dynamic items.
<custom:MyListBox x:Name="ListBox" ItemsSource="{Binding DynamicItems}" StaticItems="{Binding StaticItems}"/>
I am trying to create a custom control that will display a hyperlink button with some text below the link. The idea is to have urgent messages show up on a screen of a Silverlight page. From what I have read, I thought that I should be able to create a new control and then create some dependancy properties and bind the dynamic parts of the component pieces to them in order to allow me to add multiple instances of the custom control to my Silverlight project. Here is my XAML that defines the control
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="WhatsNew.UrgentStoryGridControl"
d:DesignWidth="608" d:DesignHeight="65" Background="White">
<UserControl.Resources>
<Style x:Key="WhatsNewTitleStyle" TargetType="HyperlinkButton">
Removed for Brevity
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Height="65" Margin="0" VerticalAlignment="Bottom" Background="White">
<StackPanel>
<HyperlinkButton Style="{StaticResource WhatsNewTitleStyle}" Content="{Binding linkText}" HorizontalAlignment="Left" VerticalAlignment="Top" NavigateUri="{Binding linkURI}" Foreground="Red"/>
<TextBlock Style="{StaticResource WhatsNewTextStyle}" Text="{Binding storyText}" Margin="0,13,0,0" d:LayoutOverrides="Height"/>
</StackPanel>
</Grid>
In the code behind, I have created three dependancy properties
Partial Public Class UrgentStoryGridControl
Inherits UserControl
Public Shared linkTextProperty As DependencyProperty = DependencyProperty.Register("linkText", GetType(String), GetType(UrgentStoryGridControl), New PropertyMetadata("Link Text"))
Public Shared linkURIProperty As DependencyProperty = DependencyProperty.Register("linkURI", GetType(String), GetType(UrgentStoryGridControl), New PropertyMetadata("link.html"))
Public Shared storyTextProperty As DependencyProperty = DependencyProperty.Register("storyText", GetType(String), GetType(UrgentStoryGridControl), New PropertyMetadata("Story Text"))
Public Property linkText() As String
Get
Return GetValue(linkTextProperty)
End Get
Set(ByVal value As String)
SetValue(linkTextProperty, value)
End Set
End Property
Public Property linkURI() As String
Get
Return GetValue(linkURIProperty)
End Get
Set(ByVal value As String)
SetValue(linkURIProperty, value)
End Set
End Property
Public Property storyText As String
Get
Return GetValue(storyTextProperty)
End Get
Set(ByVal value As String)
SetValue(storyTextProperty, value)
End Set
End Property
End Class
When I place this control on my Silverlight project using Expression Blend, I see the three properties listed in the Miscellaneous section of the properties window as I would expect. The values from the PropertyMetadata are populated as the default values for these properties. Here is the code from my Silverlight project where I leave the default values alone:
<local:UrgentStoryGridControl x:Name="urgentStory" Height="65" />
Here is the code where I try to set the values to something:
<local:UrgentStoryGridControl x:Name="urgentStory" Height="65" linkText="Test Link Text" linkURI="testpage.html" storyText="Sample Story Text" />
Either way I attempt to use the control, I'm not getting anything displayed when I launch the application. I figure that I'm missing something small but after having spent a lot of time today researching this, I'm not finding anything that would indicate what I'm missing or doing wrong.
You need to set the DataContext in your custom UserControl or else your bindings won't work.
In your UrgentStoryGridControl's constructor, you should be able to set Me.DataContext = Me