How to access a static variable from code behind? [duplicate] - c#

This question already has answers here:
Setting Label Text in XAML to string constant
(3 answers)
Closed 9 years ago.
Question: Is it possible to access a static variable from code behind to be used in XAML?
Reason: I want a single string variable to keep a menu name which will be used in different places (in code behind and also in XAML).
Example (code behind):
public partial class MainWindow : Window
{
public static readonly string menuName = "MyMenu";
... other code ...
}
Example (XAML):
<MenuItem Header="... here I want my menuName to appear ..." />

for that you would need to instantiate your class in xaml once, then you can use the static member.
it would be better to create a separate class for static variables and load it in xaml in resources.
something like this
<Window.Resources>
<!-- Create an instance of the class called MyClass -->
<my:MyClass x:Key="MyClass" />
</Window.Resources>
then use it as something like
<TextBox Text="{x:Static my:MyClass.MyProperty}" Width="500" Height="100" />
or
<TextBlock Text="{Binding Source={StaticResource MyClass},Path=MyProperty}" />
also see
XAML Binding to static classes
How to bind in XAML to a static property?

You should add it to project Resource dictionary:
go to you project -> Properties -> Resources-> Add Resource Button
then you can use it in Xaml or code behind like that:
-- XAML---
<MenuItem Header="{x:Static properties:Resources.menuName}" />
--- Code behind ----
Properties.Resources.menuName

you can not do this for the simple reason that when you bind on your property you will get an infinite nested calls to MainWindow which will generate an 'System.StackOverflowException' you should use a container class like this
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
}
public class WindowMessagesManager
{
private static string _header;
public static string Header1
{
get { return "My Header"; }
set { _header = value; }
}
}
}
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:prop="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<prop:WindowMessagesManager x:Key="window" ></prop:WindowMessagesManager>
//you can try to uncomment this and you will get an exception
<!--<prop:MainWindow x:Key="window"></prop:MainWindow>-->
</Window.Resources>
<Grid>
<Menu>
<MenuItem Height="100" Width="100" Header="{Binding Source={StaticResource ResourceKey=window}, Path=Header1}"></MenuItem>
</Menu>
</Grid>
</Window>

Related

How can I modify a UIElement.property from another class?

I'm working on a WinUI app (c# and xaml) with multiple frames and pages.
The problem is that I need to modify a UIElement property (TextBox.Text) from another class. I've been trying so many things and none of them has worked yet.
I'd be glad if someone could inspire me with some useful ways to do it. It can be anything aside from xaml data binding (<property={"Binding bindingName"}).
Thanks for the help.
In this sample code, I have 2 pages (Page1 and Page2) and 1 ViewModel (MainViewModel). Each page has a TextBox that are bound to the same property in the MainViewModel.
If you change the text in Page1, the text in Page2 will be changed, and vice versa.
NuGet packages
CommunityToolkit.Mvvm
Microsoft.Extensions.DependencyInjection
App.xaml.cs
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
namespace MultiplePagesSingleViewModel;
public partial class App : Application
{
private Window? window;
public App()
{
this.InitializeComponent();
Ioc.Default.ConfigureServices(
new ServiceCollection()
// This needs to be Singleton
.AddSingleton<MainViewModel>()
.BuildServiceProvider());
}
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
this.window = new MainWindow();
this.window.Activate();
}
}
Page1.xaml
<Page
x:Class="MultiplePagesSingleViewModel.Page1"
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:local="using:MultiplePagesSingleViewModel"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<StackPanel>
<TextBlock Text="Page 1" />
<TextBox Text="{x:Bind ViewModel.SomeText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Page>
Page1.xaml.cs
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
namespace MultiplePagesSingleViewModel;
public sealed partial class Page1 : Page
{
public Page1()
{
this.InitializeComponent();
// This MainViewModel is the same instance that Page2 gets.
ViewModel = Ioc.Default.GetRequiredService<MainViewModel>();
}
public MainViewModel ViewModel { get; }
}
Page2.xaml
<Page
x:Class="MultiplePagesSingleViewModel.Page2"
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:local="using:MultiplePagesSingleViewModel"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<StackPanel>
<TextBlock Text="Page 2" />
<TextBox Text="{x:Bind ViewModel.SomeText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Page>
Page2.xaml.cs
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
namespace MultiplePagesSingleViewModel;
public sealed partial class Page2 : Page
{
public Page2()
{
InitializeComponent();
// This MainViewModel is the same instance that Page1 gets.
ViewModel = Ioc.Default.GetRequiredService<MainViewModel>();
}
public MainViewModel ViewModel { get; }
}
MainViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
namespace MultiplePagesSingleViewModel;
// This class needs to be "partial" for CommunityToolkit.Mvvm.
public partial class MainViewModel : ObservableObject
{
[ObservableProperty]
// The CommunityToolkit.Mvvm will automatically generate
// a UI-Interactive "SomeText" property for you.
private string someText = "Default text";
}
MainWindow.xaml
<Window
x:Class="MultiplePagesSingleViewModel.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:local="using:MultiplePagesSingleViewModel"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid ColumnDefinitions="*,*">
<local:Page1 Grid.Column="0" />
<local:Page2 Grid.Column="1" />
</Grid>
</Window>
Well, finally after two weeks of research I've found and check a solution.
The solution is basically rising and catching events from the different classes. I found this information in a MSDN post. https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/how-to-publish-events-that-conform-to-net-framework-guidelines
This post is REALLY STRAIGHT FORWARD. I've created the CustomEventArgs class and then the publisher class is my controller (.cs) and my subscriber is the model (.xaml.cs).
The only problem that I've encountered is setting alll the methods and attributes as static in order to be able to call the controller function from other models. The problem is that on the raiseEvent(this, e); in the publisher class, this cannot be static so i've written null instead.
Also, I'd like to say that it works extremely fluent and with no lag or delay at all even if I constantly use it.
I hope this helps to everybody encountering the same problem.

How can I write a method that returns the value of a of xaml toggle switch?

I have two xaml toggles in separate files that I want to update simultaneously (if one is switched on the other should be too (and vice versa). My first switch in xaml is:
<Switch Grid.Column="1" x:Name="toggleSwitch1" IsToggled="true" Toggled="OnToggled"/>
Using C# how can I return a boolean value of this switch so that I can update another switch simultaneously? Then once retrieving the value, how can I update the xaml of the toggle status for the other switch?
Your Switch control means, as I can understand, that you using UWP, but I'm not sure.
Anyway, the idea is to bind both controls IsToggled properties to same property of some ViewModel:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MyWPFApp
{
public class ControlsViewModel : INotifyPropertyChanged
{
private bool switchToggled;
public bool SwitchToggled
{
get => switchToggled;
set
{
switchToggled = value;
OnPropertyChanged(nameof(SwitchToggled));
}
}
public ControlsViewModel() { }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then in XAML of both Windows set bindings to Switch control (in my example - CheckBox control):
<!-- Window 1 -->
<Window x:Class="MyWPFApp.Window1"
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:MyWPFApp"
mc:Ignorable="d"
Title="Window 1" Height="100" Width="300">
<Grid>
<CheckBox Content="Window1 CheckBox"
IsChecked="{Binding SwitchToggled}"/>
<!-- Replace IsChecked to IsToggled property -->
</Grid>
</Window>
<!-- Window 2 -->
<Window x:Class="MyWPFApp.Window2"
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:MyWPFApp"
mc:Ignorable="d"
Title="Window 2" Height="100" Width="300">
<Grid>
<CheckBox Content="Window2 CheckBox"
IsChecked="{Binding SwitchToggled}"/>
<!-- Replace IsChecked to IsToggled property -->
</Grid>
</Window>
Code-behind of both Windows in example is same:
using System.Windows;
namespace MyWPFApp
{
public partial class Window1 : Window // or public partial class Window2
{
public Window1(ControlsViewModel cvm) // or public Window2
{
InitializeComponent();
DataContext = cvm;
}
}
}
And when calling that example Windows to show from Main one, you creating ControlsViewModel instance and pass it to both:
using System.Windows;
namespace MyWPFApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var cvm = new ControlsViewModel();
new Window1(cvm).Show();
new Window2(cvm).Show();
}
}
}
So checking/unchecking (toggle/untoggle) one of them will affect another and vice versa. Also, you can change SwitchToggled from code somewhere, which would affect both controls too.
Please note, that this is just example to try explain the idea. More MVVM pattern explanations and examples you can find at MSDN.

How can you bind an attached property to DisplayMemberPath of a ComboBox?

I can't seem to find the correct syntax to allow an attached property to be used as the DisplayMemberPath of a ComboBox.
The property is SelectorSwitchedControl.NameForSelector
It's in the namespace 'LocalTest' which is mapped to the XAML prefix 'local'.
Here's the code...
<UserControl x:Class="Playground.SelectorSwitchedControlTest.SelectorSwitchedControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:glc="clr-namespace:Playground.CommonControls"
xmlns:local="clr-namespace:Playground.SelectorSwitchedControlTest"
Background="Transparent">
<Border x:Name="MainBorder"
BorderBrush="Gray" BorderThickness="1">
<DockPanel>
<glc:FixedToolBar DockPanel.Dock="Top">
<ComboBox x:Name="MainSelector"
ItemsSource="{Binding Children, ElementName=MainPanel}"
DisplayMemberPath="(local:SelectorSwitchedControl.NameForSelector)" />
</glc:FixedToolBar>
<local:SelectorSwitchedControlPanel x:Name="MainPanel" />
</DockPanel>
</Border>
</UserControl>
...which for some reason gives me the exception 'Prefix 'local' does not map to a namespace.' which I'm not sure why it's saying that as if I remove the 'DisplayMemberPath' line, the '' tag renders just like it's supposed to proving the namespace is mapped.
I've also tried all of the following...
DisplayMemberPath="local:SelectorSwitchedControl.NameForSelector"
DisplayMemberPath="(local:SelectorSwitchedControl.NameForSelector)"
DisplayMemberPath="SelectorSwitchedControl.NameForSelector"
DisplayMemberPath="(SelectorSwitchedControl.NameForSelector)"
DisplayMemberPath="LocalTest.SelectorSwitchedControl.NameForSelector"
DisplayMemberPath="(LocalTest.SelectorSwitchedControl.NameForSelector)"
I know it's just one of those days where my mind isn't working and I'm missing something simple, but it's driving me crazy! So what's the proper syntax?
DisplayMemberPath - path to the display string property for each item. Set it to "NameForSelector", not to "{Binding NameForSelector}".
<DockPanel>
<ComboBox x:Name="MainSelector" ItemsSource="{Binding Children}" DisplayMemberPath="NameForSelector" />
</DockPanel>
public class SelectorSwitchedControl
{
public string Name { get; set; }
public string NameForSelector{ get; set; }
}
I think it's just not possible to use an attached property in DisplayMemberPath with regular controls. The reason is that the property path you are using refers to an XML namespace declared in your XAML. Normally when you use the attached property syntax there is a parser context available when the XAML/BAML reader is creating the objects and this context supplies the namespace information. However DisplayMemberPath is just a string and does not capture this context, so this context is not available to supply the namespace information at the point where your property path is actually used to create a binding. From my reading of code in PresentationFramework.dll, you might be able to supply the context through the target object (the one to which your property is attached) by having it implement IServiceProvider and return a suitable IXamlTypeResolver (relevant code starts from PropertyPath.GetTypeFromName).
As a cheaper alternative, consider a template or template selector instead of DisplayMemberPath. If you want to use the default lookup mechanisms, try something along the lines of
<ItemTemplate>
<DataTemplate>
<ContextPresenter
Content="{Binding (local:SelectorSwitchedControl.NameForSelector)}"/>
</DataTemplate>
</ItemTemplate>
The correct value is
DisplayMemberPath="(local:SelectorSwitchedControl.NameForSelector)"
If that is not working then I would use Snoop (http://snoopwpf.codeplex.com/) to make sure that the value is getting set correctly.
Here is the simplest working example
Xaml:
<Window x:Class="WPFTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFTest"
Title="MainWindow" Height="350" Width="525" Loaded="MainWindow_Loaded">
<Grid>
<ComboBox Name="cb" DisplayMemberPath="(local:MainWindow.TestValue)" />
</Grid>
Code:
public static string GetTestValue(DependencyObject element)
{
return (string)element.GetValue(TestValueProperty);
}
public static void SetTestValue(DependencyObject element, string value)
{
element.SetValue(TestValueProperty, value);
}
public static readonly DependencyProperty TestValueProperty = DependencyProperty.RegisterAttached("TestValue", typeof(string), typeof(MainWindow), new FrameworkPropertyMetadata(null));
private void MainWindow_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
TextBlock tb = default(TextBlock);
for (int i = 10; i <= 15; i++)
{
tb = new TextBlock();
tb.Text = "Text for " + i;
tb.SetValue(TestValueProperty, "Property For " + i);
this.cb.Items.Add(tb);
}
}

MVVM WPF DataBinding troubleshooting

In following the MVVM architecture for WPF, learning WPF DataBindings. I have a single instance of an object instantiated at runtime with the XAML code <p:MemoryPersistentStorageBridge x:Key="persistentMemoryBridge" /> within Window Resources. I am trying to obtain data from the object instance, and plop it into a TextBox as an example, but I am not getting any text in that text box.
XAML:
<Window x:Class="UserConsole.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:p="clr-namespace:PollPublicDataStock;assembly=PollPublicDataStock"
xmlns:local="clr-namespace:UserConsole"
Title="MainWindow" Height="900" Width="800">
<Window.Resources>
<p:MemoryPersistentStorageBridge x:Key="persistentMemoryBridge" />
</Window.Resources>
<Grid Name="grid1" >
<!-- layout defintions -->
<TextBox DataContext="{StaticResource persistentMemoryBridge}" Text="{Binding Path=GetConnectionString}" Margin="0,327,31,491" Foreground="Black" Background="Yellow"/>
</Grid>
</Window>
CodeBehind:
public class MemoryPersistentStorageBridge {
public MemoryPersistentStorageBridge() {
}
public string GetConnectionString() {
return "THISTEXTSHOULDAPPEARINTEXTBOXBUTSADLYDOESNOT";
}
}
You are trying to bind to a method. You need to bind to a property. Or else use an ObjectDataProvider.
So you could do this:
public class MemoryPersistentStorageBridge {
public MemoryPersistentStorageBridge() {
}
public string ConnectionString {
get { return GetConnectionString(); }
}
public string GetConnectionString() {
return "THISTEXTSHOULDAPPEARINTEXTBOXBUTSADLYDOESNOT";
}
}
or even:
public class MemoryPersistentStorageBridge {
public MemoryPersistentStorageBridge() {
}
public string ConnectionString {
get { return "THISTEXTSHOULDAPPEARINTEXTBOXBUTSADLYDOESNOT"; }
}
}
Of course, in either case we are not handling changing the property and notifying the binding of a change.
The other option is to use the ObjectDataProvider to wrap your method. This is illustrated in the link I provided. But would look something like this:
<ObjectDataProvider ObjectInstance="{StaticResource persistentMemoryBridge}"
MethodName="GetConnectionString" x:Key="connectionString">
</ObjectDataProvider>

Binding data items on second window

Finally I decided to jump on the WPF bandwagon and decided to follow the MVVM Pattern to create my applications. I am also using Caliburn.Micro.
I found many examples of Binding data to Windows but all the examples contained just one MainWindow. I couldn't figure out how to reference and make the binding when opening a second and third window. To illustrate my problem I created a simple application. This application has two windows, the main one named ShellView and the second one named Window1View. All I need in this application is to display the content of myStr1 into the TextBox on Window1View.
Here is the code:
Views.ShellView.xaml
<Window x:Class="Test.Views.ShellView"
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>
<TextBlock x:Name="Title" />
<Button Content="Window 1" Height="31" HorizontalAlignment="Left" Margin="24,268,0,0" Name="btnWin1" VerticalAlignment="Top" Width="87" Click="btnWin1_click" />
</Grid>
</Window>
Views.ShellView.xaml.cs
namespace Test.Views
{
using System.Windows;
public partial class ShellView : Window
{
public ShellView()
{
InitializeComponent();
}
private void btnWin1_click(object sender, RoutedEventArgs e)
{
Window1View win1 = new Window1View();
win1.Show();
}
}
}
ViewModels.ShellViewModel.cs
namespace Test.ViewModels
{
using Caliburn.Micro;
public class ShellViewModel : PropertyChangedBase
{
public static string txt1 = "String 1";
public static string txt2 = "String 2";
private string title;
public string Title
{
get { return title; }
set
{
if (title != value)
{
title = value;
RaisePropertyChangedEventImmediately("Title");
}
}
}
public ShellViewModel()
{
Title = "Hello Caliburn.Micro";
}
}
}
Views.Window1View.xaml
<Window x:Class="Test.Views.Window1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
Title="Window 1" Height="300" Width="300">
<Grid>
<Label Content="TextBox 1" Height="26" HorizontalAlignment="Left" Margin="12,40,0,0" Name="label1" VerticalAlignment="Top" Width="75" />
<TextBox Height="29" HorizontalAlignment="Left" Margin="106,39,0,0" Name="txtBox1" VerticalAlignment="Top" Width="145" Text="{Binding myStr1}" />
</Grid>
</Window>
View.Window1View.xaml.cs
using System.Windows;
namespace Test.Views
{
/// <summary>
/// Interaction logic for Window1View.xaml
/// </summary>
public partial class Window1View : Window
{
public Window1View()
{
InitializeComponent();
}
}
}
ViewModels.Window1ViewModel.cs
namespace Test.ViewModels
{
class Window1ViewModel
{
public Window1ViewModel()
{
myStr1 = ShellViewModel.txt1;
}
public string myStr1 { get; set; }
}
}
Bootstrapper.cs
namespace Test
{
public class Bootstrapper : Caliburn.Micro.Bootstrapper<Test.ViewModels.ShellViewModel>
{
}
}
App.xaml
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Test.App">
<Application.Resources>
</Application.Resources>
</Application>
App.xaml.cs
namespace Test
{
using System.Windows;
public partial class App : Application
{
Bootstrapper bootstrapper;
public App()
{
bootstrapper = new Bootstrapper();
}
}
}
Any help with this would be greatly appreciated.
Thanks
Carmelo
Maybe I'm missing something because I'm not familiar with Caliburn.Micro, but I see a couple of things going on here.
First, I don't see anywhere that you're setting the DataContext of the Window1View to be an instance of Window1ViewModel.
Second, your Window1ViewModel doesn't derive from PropertyChangedBase and you don't call RaisePropertyChanged when you change the myStr1 property.
A common way to implement view model communication in MVVM is to use the Mediator Pattern. Most MVVM frameworks include a 'Messenger' class that allows you to decouple your design by publishing and subscribing to events. In Caliburn Micro, mediation is supported by the EventAggregator class.
Since you are new to MVVM, I would also recommend the following resources:
Implementing the MVVM Pattern
Advanced MVVM Scenarios
User Interaction Patterns
A few things here:
You are using code behind unnecessarily. Ideally you should aim for virtually no code behind when using MVVM. Instead of creating a btnWin1_click handler, name the button and implement a method on your view model with the same name. Caliburn.Micro will invoke the view model method based on convention.
Use x:Name rather than Name
When displaying the Window1ViewModel, you are not using Caliburn.Micro to invoke the window display. This means that no binding exists between your Window1View and Window1ViewModel. If this is a separate window, use the WindowManager type from Caliburn.Micro. Instantiate your Window1ViewModel, and use the WindowManager class to display it. Caliburn.Micro will locate the appropriate view based on conventions, and bind the view to your view model.
As mentioned, rather than reference the ShellViewModel directly in the Window1ViewModel (which couples the view models and makes Window1ViewModel less reuseable), use the mediator pattern. Caliburn.Micro comes with an EventAggregator class - you can publish the change in text from the ShellViewModel, and subscribe to the event in your Window1ViewModel.

Categories

Resources