Im having difficulty displaying a variable within a text box.
Mainly trying to understand the way that Bindings work in XAML.
I need to try to display the variable TextBoxFileName and TextBoxFilePath in the relevant fields. The information the variables are retrieving is stored within a seperate GlobalVariableStorage class. I do not want the TextBox fields to be editable so ive set them as read only. I do not want the user to be able to edit the data in these fields at all. If you have ideas for alternative display methods, please feel free to suggest.
XAML
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<!-- Placeholder for the theme dictionary -->
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Page.Resources>
<Frame Background="{StaticResource CustomAcrylicDarkBackground}">
<StackPanel>
<TextBox Width="500" Header="File Name" PlaceholderText="Name Of File" IsReadOnly="True" Foreground="White" Text=""/>
<TextBox Width="500" Header="File Location" PlaceholderText="File Location" IsReadOnly="True" Foreground="White" Text=""/>
</StackPanel>
</Frame>
CODE BEHIND
public sealed partial class SettingsPage : Page
{
public SettingsPage()
{
this.InitializeComponent();
}
public class TextBoxDisplay
{
public string TextBoxFileName = GlobalVariables.FileName;
public string TextBoxFilePath = GlobalVariables.FilePath;
}
}
You are having a difficult time because you have a wrong approach.
For instance, you didn't follow the MVVM pattern. Plus, you don't need to set the IsReadOnly to true, you just use One-Way Binding.
<TextBox Text="{Binding TextBoxFileName,Mode=OneWay}"/>
To understand and implement MVVM correctly, I suggest you to read the following links: MVVM for WPF. Although it is for WPF, but UWP is very similar to WPF, you won't get any problem.
If you want to learn MVVM, I can help you. Just send me a message on Discord: Red Wei#2396
Have fun.
Add two read-only properties to your SettingsPage class:
public sealed partial class SettingsPage : Page
{
public SettingsPage()
{
this.InitializeComponent();
}
public string TextBoxFileName => GlobalVariables.FileName;
public string TextBoxFilePath => GlobalVariables.FilePath;
}
...and bind to these:
<TextBox Header="File Name" ... Text="{x:Bind TextBoxFileName}"/>
<TextBox Header="File Name" ... Text="{x:Bind TextBoxFilePath}"/>
Related
I added in my code copying the first TabItem along with all the controls. But now how to reference to these controls to make the appropriate modifications (I mean change label text)? Do I must need make new class and binding ?
I want clone tab item to store data in labels about for example other apps.
XAML code:
<TabControl x:Name="MainTabControl" x:FieldModifier="public">
<TabItem x:Name="TabItem1" x:FieldModifier="public" Header="Tab 1">
<Grid>
<Label x:Name="Label1" Content="Test 1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" FontSize="18"/>
<Label x:Name="Label2" Content="Test 2" HorizontalAlignment="Left" Margin="11,44,0,0" VerticalAlignment="Top" FontSize="18"/>
<Button x:Name="Button1" Content="Show Open Dialog" IsEnabled="True" HorizontalAlignment="Left" Margin="11,185,0,0" VerticalAlignment="Top" Width="194" Click="Button1_Click"/>
</Grid>
</TabItem>
C# code:
public MainWindow()
{
InitializeComponent();
mainWindow = this;
TabItem tab2 = TrycloneElement(TabItem1);
if (tab2 != null) MainTabControl.Items.Add(tab2);
}
public static T TrycloneElement<T>(T orig)
{
try
{
string s = XamlWriter.Save(orig);
StringReader stringReader = new StringReader(s);
XmlReader xmlReader = XmlTextReader.Create(stringReader, new XmlReaderSettings());
XmlReaderSettings sx = new XmlReaderSettings();
object x = XamlReader.Load(xmlReader);
return (T)x;
}
catch
{
return (T)((object)null);
}
}
You can access the controls by walking the visual tree using Content, Children or other properties.
TabItem tab2 = TrycloneElement(TabItem1);
var grid = (Grid) tab2.Content;
var label1 = (Label) grid.Children[0];
var label2 = (Label) grid.Children[1];
var button = (Button) grid.Children[2];
An alternative is to use the VisualTreeHelper and convenience helper methods like described here:
How can I find WPF controls by name or type?
Now, these methods work, but you do not need them. You are actually trying to reinvent the wheel. Reusing the same visual structure with different data can be achieved with data templating in a convenient way.
Data Templating Overview
You can use the same technique for your TabControl. I will show you a quick conceptual example that would even comply with the MVVM pattern. This example is not complete. It serves as a base for you to learn about an alternative to your current approach. You can of course also make this approach work in code-behind with a few modifications.
Create a data class like below with your application information. Here I assume that it is not editable, otherwise you need to implement INotifyPropertyChanged in order to update the user interface on changes.
public class AppInfo
{
public AppInfo(string name, string description)
{
Name = name;
Description = description;
}
public string Name { get; }
public string Description { get; }
}
Expose a list of AppInfo (or an ObservableCollection if the collection is changed at runtime).
public List<AppInfo> AppInfos { get; }
Initialize the list appropriately and add your items, for example:
AppInfos = new List<AppInfo>
{
new AppInfo("Visual Studio", "A popular IDE."),
new AppInfo("Calculator", "Does the math for you."),
new AppInfo("Chrome", "A web browser.")
};
The tab control would bind its ItemsSource to the AppInfos.
<TabControl ItemsSource="{Binding AppInfos}">
In order to create a reusable visual representation, create a DataTemplate as ItemTemplate (this is the tab header) and a ContentTemplate (this is the tab content).
<TabControl ItemsSource="{Binding AppInfos}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:AppInfo}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:AppInfo}">
<Grid>
<Label Content="{Binding Name}"
HorizontalAlignment="Left"
Margin="10,10,0,0"
VerticalAlignment="Top"
FontSize="18"/>
<Label Content="{Binding Description}"
HorizontalAlignment="Left"
Margin="11,44,0,0"
VerticalAlignment="Top"
FontSize="18"/>
<Button Content="Show Open Dialog"
IsEnabled="True"
HorizontalAlignment="Left"
Margin="11,185,0,0"
VerticalAlignment="Top" Width="194"
Command="{Binding DataContext.OpenCommand, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}"
CommandParameter="{Binding}"/>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Now, both templates are reused for each item in the AppInfos collection. The data template uses bindings to the corresponding properties, which automatically pick up the values for each item.
The Button is special here, since it can be clicked to execute an action. This action is probably the same for all items, except that it needs a concrete item to work with. You can encapsulate this logic in commands.
MVVM - Commands, RelayCommands and EventToCommand
This is another important aspect that can make your life easier, but is beyond the scope of this question. There are lots of tutorials out there. You could expose another property:
public ICommand OpenCommand{ get; }
The button in XAML binds its Command to this property (RelativeSource is needed to get the right data context for binding). The CommandParameter will pass the current AppInfo item to the command. In code, you could then execute your custom logic on this item. No need to know about user interface elements, only data.
Disclaimer: I know that this is pretty overwhelming. That is why I provided an immediate (dirty) solution to your issue and an example with links to resources to gradually learn a better way with concrete samples for your exact problem. Once you get used to MVVM, data binding and data templating, you will see how much easier more complex issues can be solved and what the benefits are in terms of maintainability and reuseability.
I'm developing a WPF app using MVVM pattern with Caliburn.Micro
I have a config file that contains positions of where XAML elements should be inside of a StackPanel
# in this case RU_ELEMENT should be at the top, EN_ELEMENT second and DE_ELEMENT last
EN_ELEMENT = 1
DE_ELEMENT = 2
RU_ELEMENT = 0
This seems to be pretty basic yet I'm unable to find a way to do this. I found this thread: change the children index of stackpanel in wpf but changing it this way seems to be too complicated for what I am after. I just need to set an index of an element from a variable. I feel like there should be a much simpler way. I'm also ok with using some other, perhaps more appropriate layout panel than StackPanel.
XAML:
<!-- Language1 -->
<TextBlock Text="English" Foreground="DarkGray" FontSize="16"/>
<TextBox
VerticalAlignment="Top"
Height="150"
Text="{Binding SelectedItem.ValueEN, UpdateSourceTrigger=PropertyChanged}"
cm:Message.Attach="[Event GotFocus] = [Action FocusedTextBox('english')]" />
<!-- Language2 -->
<TextBlock Text="German" Foreground="DarkGray" FontSize="16"/>
<TextBox
VerticalAlignment="Top"
Height="150"
Text="{Binding SelectedItem.ValueDE, UpdateSourceTrigger=PropertyChanged}"
cm:Message.Attach="[Event GotFocus] = [Action FocusedTextBox('german')]" />
On a side note: I find WPF and C# in general to have much less discussions and "how to" guides than all of my previous languages (Java, Python, JS) so researching things online is usually a dead end for me. I'm not sure to why that is since C# is a very popular language but I'm really struggling with finding help online.
A solution could be to use an ItemsControl that would host the xaml elements. You can bind the items like <ItemsControl ItemsSource="{Binding ListOfItems} ...
Then you could easily sort the items in the corresponding ViewModel. Like so:
public BindableCollection<YourElement> ListOfItems {get;set;}
...
ListOfItems.Sort()
Note that YourElement class should have a comparator.
EDIT: As per request I'll explain it more detailed:
In your Xaml you have to declare a ItemsControl like so:
<ItemsControl ItemsSource="{Binding ListOfItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Language}" Foreground="DarkGray" FontSize="16"/>
<TextBox
VerticalAlignment="Top"
Height="150"
Text="{Binding TextValue, UpdateSourceTrigger=PropertyChanged}"
cm:Message.Attach="[Event GotFocus] = [Action FocusedTextBox($this)]" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And in your backend you should first create a class that's going to represent your item in the ItemsControl. For example:
public Class MyItem{
public string Language {get;set;}
public string TextValue {get;set;}
}
Finally in your ViewModel you'll need to create the list of items that you bind with the ItemsControl like so:
public BindableCollection<MyItem> ListOfItems {get;set;}= new BindableCollection<MyItem>();
//here you can add them in the order that is specified by the config file
public void LoadItems(){
ListOfItems.Add(new MyItem{Language="English"});
ListOfItems.Add(new MyItem{Language="Russian"});
ListOfItems.Add(new MyItem{Language="German"});
}
public void FocusedTextBox(MyItem item){
//do here whatever you want
}
So, I made a really simple attempt to try out data binding from a property of a class that I have, but, for whatever reason, the code actually do anything. It's not throwing any errors, but something must not be working right. I'm just currently testing if it'll behave like I want it to, which, in this case, will set the opacity of a rectangle to zero. Here's the xaml for the Data Template that doesn't seem to want to respond correctly:
<HubSection x:Name="China" Width="440" Height="460" Background="#FF343434" Header="China" IsHeaderInteractive="True" Tapped="{x:Bind HubSectionTapped}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="50,0,0,0">
<DataTemplate x:DataType="data:MainPageView">
<Grid Height="460" Width="410" VerticalAlignment="Bottom" x:Name="ChinaBackground">
<Image Source="Assets/chinaFlag.bmp" x:Name="ChinaFlag"/>
<Rectangle x:Name="ChinaSelected_Rect" Width="410" Height="30" VerticalAlignment="Bottom" Fill="BlueViolet" Opacity="{x:Bind Opacity1}"/>
</Grid>
</DataTemplate>
</HubSection>
And here's the code behind:
public MainPageView TheMainPageView;
public MainPage()
{
this.InitializeComponent();
timer = new DispatcherTimer();
timer.Tick += Timer_DyanmicResize;
timer.Tick += Timer_SelectionIndicator;
timer.Start();
TheMainPageView = new MainPageView ();
}
And finally, here's the class MainPageView that's referenced:
public class MainPageView
{
public int Opacity1 {get; set;}
public int Opacity2 {get;set;}
public int Opacity3 { get; set; }
public MainPageView()
{
this.Opacity1 = 0;
this.Opacity2 = 0;
this.Opacity3 = 0;
}
}
In the XAML I included the xmlns:data="using:TestApp.Models" (models is the folder in which the class MainPageView is housed). As I said, it's not throwing errors, but it's not doing anything either, so I'm a bit at a loss of where to start addressing this because there aren't any errors to trace back. Thanks in advance for any help you guys can provide
HubSection uses a DataTemplate to define the content for the section, content can be defined inline, or bound to a data source. When using binding in this DataTemplate, we need set DataContext property of HubSection to provide data source for the DataTemplate.
{x:Bind} does not use the DataContext as a default source—instead, it uses the page or user control itself. So it will look in the code-behind of your page or user control for properties, fields, and methods.
This is right when you use {x:Bind} directly in page or user control. While Inside a DataTemplate, there is a little difference.
Inside a DataTemplate (whether used as an item template, a content template, or a header template), the value of Path is not interpreted in the context of the page, but in the context of the data object being templated. So that its bindings can be validated (and efficient code generated for them) at compile-time, a DataTemplate needs to declare the type of its data object using x:DataType.
For more information about Data binding in UWP, please check Data binding in depth.
To fix your issue, you just need to set DataContext in HubSection like following:
<HubSection x:Name="China" Width="440" Height="460" Background="#FF343434" Header="China" IsHeaderInteractive="True" Tapped="{x:Bind HubSectionTapped}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="50,0,0,0" DataContext="{x:Bind TheMainPageView}">
<DataTemplate x:DataType="data:MainPageView">
<Grid Height="460" Width="410" VerticalAlignment="Bottom" x:Name="ChinaBackground">
<Image Source="Assets/chinaFlag.bmp" x:Name="ChinaFlag"/>
<Rectangle x:Name="ChinaSelected_Rect" Width="410" Height="30" VerticalAlignment="Bottom" Fill="BlueViolet" Opacity="{x:Bind Opacity1}"/>
</Grid>
</DataTemplate>
</HubSection>
Here when using {x:Bind} in HubSection, it uses the page itself as its data source as HubSection is in the page directly. So it can get TheMainPageView field in the code-behind. But for the {x:Bind} in DataTemplate, it can't as
its data source is the data object being templated not the page. So we need to provide this data object by setting DataContext property of HubSection.
Check you output window for errors but I imagine you might see a binding error in there. Opacity is a double, you are using an int so will get a type conversion error.
Update: I've updated the code based on your help so far, and still no luck. When the application loads the ListBox has no items. I assign junk values to Customers in the windows's contructor, and then am also trying to set the ListBox's DataContext as follows:
CustomerList.DataContext = Customers;
--- Original Question (with updated code) ---
I'm having trouble with databinding in a WPF project.
I have a class, Customer, as follows:
public class Customer
{
public String Name { get; set; }
public String Email { get; set; }
}
In my XAML's code behind I have a collection of customers as follows:
public List<Customer> Customers { get; set; }
I'm trying to bind each customer to a ListBox with a ListItemTemplate displaying the customer's information (name/email) in TextBoxes along with a button which locks/unloacks the TextBoxes (sets the IsEnabled property to true or false).
What's the best way to go about this?
So far I've been tryingt he following with no success.
In the XAML I currently have the following (ignoring the toggle part for now, I'm just trying to get the collection itself to be listed.):
<Window.Resources>
<CollectionViewSource x:Key="Customers" Source="{Binding Path=Customers, Mode=TwoWay}"/>
<DataTemplate x:Key="Customer">
<StackPanel Orientation="Horizontal">
<TextBox Content="{Binding Name}" />
<TextBox Content="{Binding Email}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Source={StaticResource Customers}}"
ItemTemplate="{StaticResource ResourceKey=Customer}"
Name="CustomerList"
Height="300" />
</StackPanel>
You need to change
ItemsSource="{Binding Source=Customers}"
to
ItemsSource="{Binding Source={StaticResource Customers}}" DataContext="{StaticResource Customers}"
Code similar to the updated one works for me after changing
<TextBox Content="{Binding Name}" />
to
<TextBox Text="{Binding Name}" />
As TextBox doesn't have Content property(like a Label), the former refused to compile in VS.
Well, it is set to Text in definition:
[ContentPropertyAttribute("Text")]
public class TextBox : TextBoxBase, IAddChild
But I thought it is only used between the brackets(<TextBox>Like so</TextBox>)?
Could this be the source of the problem?
Try setting the ItemsSource of your CustomerList as follows: ItemsSource="{Binding}". You've set the DataContext of the ListBox to the list of customers, you need to set the ItemsSource to the same collection, hence, the direct binding.
Another thing that you can do, in case you prefer to use the CollectionViewSource, is to set the DataContext of your window to the same class DataContext=this, because without this, the resource definition won't be able to locate the "Customers" collection that you defined in the code behind. If you do this, however, you don't need CustomerList.DataContext = Customers; because you're directly assigning the ItemsSource to a static resource, not relatively to the DataContext.
One more thing. I think you should give the CollectionViewSource and the corresponding collection in the code behind different names. This isn't going to cause a runtime issue, but it makes it hard to maintain the code ;)
Hope this helps :)
Given the following Xaml:
<Window.Resources>
<System:String x:Key="StringValue"></System:String>
</Window.Resources>
<Grid>
<ComboBox Margin="137,101,169,183" ItemsSource="{Binding collection}" SnapsToDevicePixels="True" IsHitTestVisible="true">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Command="{Binding CheckCommand}" IsChecked="{Binding IsChecked}" Content="{Binding Name}"/>
<TextBlock Text="{StaticResource StringValue}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
What I want is for the Textblock Text to be bound to a static resource, that is databound to a value on the ViewModel. The issue is System.String appears to not allow databinding. ANybody know of a way to do this? For context, the TextBlock needs a different itemssource than that of its parent combobox.
Thanks.
String doesnt allow binding because it is not a DependencyObject (and doesnt implement INotifyPropertyChanged)
but why dont you just bind directly to the Value in the ViewModel?
if you cannot bind to a ViewModel (think about RelativeSource with searching Parent type) you can implement a wrapper (which implements INotifyPropertyChanged to get the changes in the object)
Example wrapper class:
public class BindWrapper<T> : INotifyPropertyChanged
{
private T _Content;
public T Content
{
get
{
return _Content;
}
set
{
_Content = value;
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
how to instantiate and bind in XAML:
<Window.Resources>
<local:BindWrapper x:Key="wrapper" x:TypeArguments="System:String">
<local:BindWrapper.Content>
<System:String>huuu</System:String>
</local:BindWrapper.Content>
</local:BindWrapper>
</Window.Resources>
<TextBlock Text="{Binding Source={StaticResource wrapper}, Path=Content}" />
To clarify, A System.String has no dependency properties so you can't bind it anything. I think you need a convertor so your TextBlock can bind to the View Model. What type of ObservableCollection do you have on the View Model?
EDIT If you just want to bind a simple string to the text property this is the wrong answer. If you want to bind to formatted text, read on.
I was having this problem before. I wanted to bind my TextBlock to a string resource in my properties. I ended up subclassing TextBlock to BindableTextBlock and making and a Convertor for string to an Inline list.
Question and Answers here.
It may seem a little involved, there ought to be an easier way. However I've resused the control several times whenever I've needed to bind to some formatted text and it works. Hopefully you can benefit from my work, and perhaps improve.