I have a UserControl that looks like this:
<UserControl x:Class="Test3.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Name="mybutton" Content="Button Content"/>
</Grid>
</UserControl>
And a main window that uses it like so:
<Window Name="window_main" x:Class="Test3.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test3">
<StackPanel>
<Label Name="mylabel" Content="Old Content"/>
<local:UserControl1/>
</StackPanel>
</Window>
What I want to happen, is for mybutton's click event handler to set the content of mylabel to "New Content". However, it appears that this is impossible. Is there in fact a way to do this?
I have chosen to answer this myself since my solution ended up being a bit more complete. I don't fully understand the "right" way to do this, but this is how I did it:
Window1 window_reference = (Window1)(Window1.GetWindow((Button)sender));
After this, the children (such as other xaml controls) of the main window can be seen at compile-time.
Additionally, a more direct way of doing this is to have a public member of the UserControl like so:
namespace UIDD_Test
{
public partial class UserControl1 : UserControl
{
public Window1 window_reference;
public UserControl1()
{
InitializeComponent();
}
}
}
Then whenever appropriate, you can set that member to reference whatever window you want. In my case I have a Window1 class which is derived from Window, so I can set that member of the UserControl1 class like so:
myusercontrol.window_reference = window_main;
Where I've set up the xaml like so:
<local:UserControl1 x:Name="myusercontrol"/>
And window_main is the Name of the main window (it's a Window1 class).
There are several solutions:
The quick and dirty: on mybutton's click event handler, find the parent Window using VisualTreeHelper, then do a ((Label) window.FindName("mylabel")).Content = "New Content".
The clean WPF way: create a new class, add a property object LabelContent and a property ICommand ChangeContentCommand, that will change LabelContent on execution. Set this class as the DataContext of the window, bind the Content of mylabel to LabelContent and the Command property of mybutton to ChangeContentCommand (the user control will inherit the data context).
The simplest way to do what you describe is to take advantage of event routing and just add a handler in the Window XAML:
<StackPanel ButtonBase.Click="Button_Click">
<Label Name="mylabel" Content="Old Content"/>
<local:UserControl1/>
</StackPanel>
and the handler method:
private void Button_Click(object sender, RoutedEventArgs e)
{
mylabel.Content = "New Content";
}
I suspect you probably have some more complications to this in your real application so you may need to do more to verify that the click is coming from the correct button by checking some properties on it.
Related
I am very new to AvaloniaUI.
I am really struggling to change a text when I click a button.
Here is my code:
<Window xmlns="https://github.com/avaloniaui"
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" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReadyForWar_Launcher.MainWindow"
Title="ReadyForWar_Launcher">
<StackPanel>
<TextBlock Name="TestBlock">Show my text here!</TextBlock>
<Button Command="{Binding RunTheThing}" CommandParameter="Hello World">Change the Text!</Button>
</StackPanel>
</Window>
Here is my MainWindow.xaml.cs:
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ReadyForWar_Launcher
{
public class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void RunTheThing()
{
}
}
}
Inside RunTheThing I don't know how can I select the TextBlock with Name="TestBlock" and change the text to "Hello World".
Can you please help me out on this ?
There are two approaches, the recommended one and straightforward one.
Recommended: Use MVVM pattern. Create a view model with ButtonTextProperty and RunTheThing command, make the command to change the property, assign that model to the DataContext and bind your button text and command to view model properties. The MVVM approach is basically the same as in WPF, so you can use documentation and tutorials from there (that applies to most of the Avalonia, BTW). For example, here is a good one (not advertising, 4th link from google).
Straightforward (aka winforms-way): add x:Name="MyButton" to your button and use this.FindControl<Button>("MyButton") after calling AvaloniaXamlLoader.Load(this);. This will give you a Button reference that you can manipulate from code. Instead of using commands, you can just subscribe to the click handler directly from codebehind, add public void MyButton_OnClick(object sender, RoutedEventArgs args){} to your MainWindow class and add replace Command and CommandParameter with Click="MyButton_OnClick". That way button click will trigger your event handler.
Note, that the second approach doesn't scale well with the application size and suffers from code complexity when handling lists.
I am very sorry that this question is very basic. I just learned WPF and I failed to make simple two way binding to textbox.text to string property.
XAML Code:
<Window x:Class="WpfApplication1.MainWindow"
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"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="StuInfo">
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="10,26,0,0" TextWrapping="Wrap" Text="{Binding Path=str,Mode=TwoWay}" VerticalAlignment="Top" Width="120"/>
<Button x:Name="button" Content="Check" HorizontalAlignment="Left" Margin="10,67,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
</Grid>
C# Code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
str = "OK";
}
public string str { get; set; }
private void button_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine(str);
}
}
First, the textbox does not show "OK", but it is blank. Then, I typed a different text into the textbox, for ex:"blablabla" without the quotes. Then I click the button to check if my str property has been updated. Apparently, str still contains "OK".
What did I do wrong here? What did I miss to make the binding work?
As a newcomer to WPF, all this Binding and DataContext jazz can be quite confusing. Let's start with your binding expression first...
<TextBox Text="{Binding Path=str, Mode=TwoWay}"/>
What this is saying is that you want to bind your Text property to whatever the DataContext of the TextBox is. DataContext is essentially the "thing" your TextBox is getting it's data from. Now here's the rub. DataContext is inherited from the element "above" it in the visual tree if not explicitly set. In your code, TextBox inherits it's DataContext from the Grid element, which in turn inherits it's DataContext from the Window element. Seeing that DataContext is not set in your Window the default value of the DataContext property will be applied, which is null. The DataContext is also not set in any of the child elements of your window, which, via inheritance, will set the DataContext of all children of that window to null.
It is important to note that you've left out the Source property in your binding expression.
<TextBox Text="{Binding Source=left_out, Path=str, Mode=TwoWay}"/>
When this property is left out, the binding's source is implied to be the elements DataContext, which in this case is null, for the reasons mentioned above. Basically, what your expression is saying here is that you want to bind your text property to DataContext.str which resolved by WPF is null.str.
OK, cool. Now, how do we set the DataContext of your TextBox.Text binding to the Code Behind for the window so we can get at that str property? There are several ways to do this, but for our purposes we'll focus on setting it explicitly in the binding of the TextBox.Text property. Now, there are three different "source" type properties of bindings. "Source" being where we want our control/element's binding to get it's data from. We have Source, RelativeSource, and ElementName. We're only going to focus on ElementName here, but the others are essential to research and understand.
So, let's name our Window element so we can access it through the ElementName property.
<Window x:Class="WpfApplication1.MainWindow"
x:Name="_window"
...
Now we can set the ElementName property on the TextBox.Text binding to refer to the window.
<TextBox Text="{Binding ElementName=_window, Path=str, Mode=TwoWay}"/>
This means the binding will look for the _window.str property when trying to resolve it's binding. At this point, you still probably won't see your str value reflected in the TextBox. This is because it's value is set after the InitializeComponent method in the window's constructor. This function is where bindings are resolved for the first time. If you were to set the value of str before calling InitializeComponent, you would see the value reflected in the TextBox.
This brings us to Dependency Properties. For now, just know that Dependency Properties have built in change notification, which your binding needs so it "knows" when the binding has changed and when to resolve the binding value again. Yes, you could use INotifyPropertyChanged in your code behind, but there are good arguments for using DependencyProperties in this case, which will only confuse the issue at this point. But, it is another one of those things that is essential to understand.
Here is the code for a DependencyProperty for your str property.
public static readonly DependencyProperty StrProperty
= DependencyProperty.Register("Str", typeof(string), typeof(MainWindow),
new FrameworkPropertyMetadata(FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Str
{
get{return (string)GetValue(StrProperty);}
set{SetValue(StrProperty,value);}
}
Now you'll be able to set the value like such and have it reflect through the binding to your TextBox.
public MainWindow()
{
InitializeComponent();
Str = "OK";
}
At this point, all should be well. I hope this helps out. It took me a while get the hang of WPF. My suggestion would be to read as much as you can on DataContext, Binding, and DependencyProperty as these are the core of WPF. Good luck!
The problem is that, you dont bind to codebehind of Window, but to DataContext.
Try this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new DC();
}
public class DC
{
public string str { get; set; }
public DC()
{
str = "OK";
}
}
}
Normally, you would have two different files, but for test, you can do it in one file.
After that, your DC (DataContext) should implement INotifyPropertyChanged interface.
Try to find some article about MVVM like this http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial
I have a Window with a ContentControl inside. I want to show multiple view-filling UserControls like a wizard with multiple steps. Those UserControls need their own ViewModel and the possibility to replace themselves with another UserControl in the Window's ContentControl.
I want to work with the MVVM pattern and am currently struggling how to access the Window's ViewModel from the ViewModel of the UserControl.
Here is the simplified code I have so far. The content changing works without any problem when I change it inside the main ViewModel:
Window XAML:
<Grid>
<ContentControl Content="{Binding CurrentView}" />
</Grid>
Window ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private object currentView;
public object CurrentView
{
get { return currentView; }
private set
{
currentView = value;
OnPropertyChanged(); // <- Property name is set automatically, so no parameter needed
}
}
public MainWindowViewModel()
{
this.CurrentView = new UserControl1(); // Initial view to show within the ContentControl
}
}
UserControl1 XAML:
<UserControl>
<Grid>
<Button Command="{Binding SwitchToUserControl2}">Switch content</Button>
</Grid>
</UserControl>
Now I have the following "thinking problems":
If I set the DataContext of the UserControl to its ViewModel, I cannot access the MainWindowViewModel to change the CurrentView Property to UserControl2.
If I don't set the DataContext of the UserControl, I automatically inherit the correct ViewModel for Binding the Command to change the Content but haven't instantiated the ViewModel of the UserControl. I need this because many actions of the UserControl should be handled within it's own ViewModel.
In my understanding it is neccessary to have access to both ViewModels from the view but have no clue how to achieve this.
I would not have the MainWindowViewModel create a view, but rather create your first ViewModel. The ViewModel could then use events or any other mechanism to notify that it should transition to the next step.
The View portion can be handled easily in that case via DataTemplates that map the ViewModel to the appropriate View. The advantage here is that the ViewModel never knows about the View used to render it, which stays "pure" in an MVVM perspective. Right now, your ViewModel is manipulating the View layer, which is an MVVM violation.
Reed's answer is correct, and is one way to solve your problem, create the ViewModel of the control in the MainWindow, hook up the events and bind the ViewModel to the user control via a DependencyProperty.
To allow the binding of the ViewModel to work, make sure you do not set the DataContext in the Constructor of the UserControl or on the Root element of the Xaml of the UserControl. Instead, set the DataContext on the first content element of the UserControl. This will allow external bindings to the UserControl to continue working while the DataContext of the UserControl is what you want.
<UserControl x:Class="StackOverflow._20914503.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:this="clr-namespace:StackOverflow._20914503"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:UserControl1}}, Path=ViewModel}">
</Grid>
</UserControl>
With regards to swapping controls in and out, Reed is again correct and DataTemplates are the way to go.
Another way to solve your communications problems is to use RoutedEvents. Create a RoutedEvent with in the application and since the event will have no real association to a ui element, lets create a class to publish the routed event.
public static class EventManagement
{
public static readonly RoutedEvent ChangeViewEvent = EventManager.RegisterRoutedEvent("ChangeView", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UserControl));
}
Now, in each of the UserControls (and it must be done within the code behind of a UserControl), you can call RaiseEvent which is implemented in the UIElement class. In the following code, I am picking up an event from the ViewModel of my UserControl and firing the RoutedEvent
private void ViewModel_ChangeEvent(object sender, EventArgs e)
{
RaiseEvent(new RoutedEventArgs(EventManagement.ChangeViewEvent));
}
In my main window, without know where the RoutedEvent is going to be fired from, I can add a handler to the Routed event like so
public MainWindow()
{
InitializeComponent();
this.AddHandler(EventManagement.ChangeViewEvent, new RoutedEventHandler(SomeControl_ChangeView));
}
private void SomeControl_ChangeView(object sender, RoutedEventArgs routedEventArgs)
{
}
.Net will handle the routing of the event for you based on the RoutedEvent registration.
The advantage of this approach is the separation the functionality. Everything works without knowing anything else. You can use triggers to insert UserControl into the MainWindow, they can all Raise the same RoutedEvent, and the MainWindow will handle them all.
To Summarise the flow of control. The ViewModel of the UserControl raises a standard CLR event that the UserControl handles. The UserControl Raises the RoutedEvent. .Net Bubbles the event up to the main window. The main window receives the event via its handler.
A couple of points to note.
1. The default routing strategy for RoutedEvents is Bubbling (from lowest element, say a button, to the highest, say MainWindow).
1. An event will stop once a handler has flagged the event as Handled.
1. Routing is mostly done via the Visual Tree.
If necessary, I can post the component parts of my example.
I hope this helps.
I have this simple UserControl:
<UserControl x:Class="WPFTreeViewEditing.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="Hello, world!" KeyDown="TextBlock_KeyDown" />
</Grid>
</UserControl>
I want to handle TextBlock.KeyDown event. So, I've added an event handler to the code-behind:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void TextBlock_KeyDown(object sender, KeyEventArgs e)
{
MessageBox.Show("Key up!");
}
}
but it doesn't fire. What's wrong?
UPDATE.
PreviewKeyDown doesn't fire too.
This UserControl is used in HierarchicalDataTemplate then:
<Window x:Class="WPFTreeViewEditing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFTreeViewEditing"
Title="MainWindow" Height="265" Width="419">
<Grid>
<TreeView ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ViewModel}" ItemsSource="{Binding Items}">
<local:UserControl1 />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
From the documentation for UIElement.KeyDown:
Occurs when a key is pressed while focus is on this element.
You're using TextBlock which doesn't have the focus, so your KeyDown event will be handled by another control.
You can switch to TextBox and appy some styles so it'll look and behave like TextBlock, but you'll be able to get the focus and handle the event.
You should use PreviewKeyDown event instead of KeyDown event.
Ok, even though this question was posted a long time ago I had the same problem and found a way to get KeyDown events working, though it might not be what you're looking for I'll post the code to help future people with the same problem.
First thing first a KeyDown event handler in an Xaml Object will only fire off if that object has focus. Therefore you need a CoreWindow event handler, it's kind off the same thing but it will always run no matter what object or thing has focus. The following will be the code.
//CLASS.xaml.h
ref class CLASS{
private:
Platform::Agile<Windows::UI::Core::CoreWindow> window;
public:
void KeyPressed(Windows::UI::Core::CoreWindow^ Window, Windows::UI::Core::KeyEventArgs^ Args);
//CLASS.xaml.cpp
CLASS::CLASS(){
InitializeComponent();
window = Window::Current->CoreWindow;
window->KeyDown += ref new TypedEventHandler
<Windows::UI::Core::CoreWindow^, Windows::UI::Core::KeyEventArgs^>(this, &CLASS::KeyPressed);
};
void CLASS::KeyPressed(Windows::UI::Core::CoreWindow^ Window, Windows::UI::Core::KeyEventArgs^ Args){
SimpleTextBox->Text = Args->VirtualKey.ToString();
};
Basically you want a value to hold your window and use that to create a new TypedEventHandler. For safety you'll generally want to do this in your class' constructor a function that's only called once the moment the class starts (I still prefer the constructor though).
You can use this method to create an event handler for any event. Just change the "KeyDown" for another attribute like KeyUp, PointerMoved, PointerPressed and change the "&CLASS::KeyPressed" to the name of the function you want to be fired the moment you get an event of a corresponding type.
I'm new in WPF and C#. I know a lot of VB.NET and I'm used to the way when I call a form object like textboxes, etc. I'm calling it from another form. Now, I'm using WPF, I'm confused. Because I have a Main Window. And I want to add and item to a listbox in the Main Window from a Class. In VB.Net , its just like this.
IN FORM2
Form1.Textbox.Text = "";
Wherein I can't do it in WPF. Can someone please Help me. Thanks!
WPF windows defined in XAML have their controls publicly accessible from other classes and forms, unless you specifically mark them with the x:FieldModifier attribute as private.
Therefore, if you make an instance of your main window accessible in another class, be it a Window or anything else, you'll be able to populate controls from within this second class.
A particular scenario is when you want to update the contents of a control in your main window from a child window that you have opened on top of it. Is such a case, you may set the child window's Owner property to the current, main window, in order to access it while the child is visible. For instance, let's say you have defined these two windows:
// MainWindow
<Window x:Class="TestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Name="mainListBox" Height="250" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
<Button Content="Open Another Window" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="20" Click="OpenAnotherWindow_Click"/>
</Grid>
</Window>
and
// AnotherWindow
<Window x:Class="TestApplication.AnotherWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AnotherWindow" Height="300" Width="300">
<Grid>
<Button Content="Add New Item to Main Window" HorizontalAlignment="Center" VerticalAlignment="Center" Click="AddNewItem_Click"/>
</Grid>
</Window>
each in its own XAML file.
In MainWindow's code behind, inside the button click handler, you show an instance of AnotherWindow as a dialog and set its Owner property to MainWindow's instance:
private void OpenAnotherWindow_Click(object sender, RoutedEventArgs e)
{
AnotherWindow anotherWindow = new AnotherWindow();
anotherWindow.Owner = this;
anotherWindow.ShowDialog();
}
Now, you can access the MainWindow's instance from AnotherWindow's Owner property, in order to add a new item to the ListBox control defined on it, in the button click handler in AnotherWindow's code behind:
private void AddNewItem_Click(object sender, RoutedEventArgs e)
{
MainWindow mainWindow = Owner as MainWindow;
mainWindow.mainListBox.Items.Add(new Random().Next(1000).ToString());
}
It simply adds a new random number to the ListBox, in order to show how the code accesses and modifies the control's data in MainWindow.
Pure WPF solution, but also may be easiest in your case, is using a Data Binding in WPF.
Every form's control is binded to some data on ModelView (pure MVVM approach) or to data (more or less like yuo can do it in WindowsForms). So the "only" thing you have to do is to read/write data binded to controls on UI of that form.
For example, you have TextBox on Windows and want to read a data from it.
This TextBox is binded to some string property of the class that is responsible for holding the data for the controls on that form (just an example, in real world could be 1000 other solutions, based on developer decisions). So what you need, is not to say: "window give textbox" and after read TextBox's content, but simply read binded string property.
Sure it's very simply description of a stuff. But just to give you a hint. Follow databinding link provided above to learn more about this stuff. Do not afraid of a lot of stuff there, it's after all is not a complicated idea and also pretty intuitive. To make that stuff to work in simply case you will not need to make huge efforts by me. The stuff becomes really complex when you end up into real world applications.
This will get all active windows:
foreach (Window item in Application.Current.Windows)
{
}