WPF menu - Dynamic show and hide content - c#

I need to create interactive menu. When option was choosen, I want to show appropriate content.
For example when option "Schemat bazy Northwind" was clicked, to my grid should be added Image. When another option was choosen previous content is removed and etc.
The only thing that comes to my mind is create functions which at the beginning clear grid and later add content(Is it possible?).
Please there anybody could direct me to solve this problem.
<Window x:Class="Northwind.AdminPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Panel administratora" WindowState="Maximized">
<StackPanel Name="bindingData">
<StatusBar>
<TextBlock FontSize="15" Text="{Binding ServerName}" Margin="0 0 30 0"></TextBlock>
<TextBlock FontSize="15" Text="{Binding ConnectionStatus}" Margin="0 0 30 0"></TextBlock>
<Label FontSize="15" Name="lblClock"></Label>
</StatusBar>
<DockPanel Height="55">
<Menu DockPanel.Dock="Top">
<MenuItem Header="Baza" Margin="10" FontSize="15"></MenuItem>
<MenuItem Header="Pomoc" Margin="10" FontSize="15">
<MenuItem x:Name="itemSchema" Header="Schemat bazy Northwind" Click="itmSchema_Click_1"></MenuItem>
</MenuItem>
</Menu>
</DockPanel>
<Grid x:Name="mainContent">
<!--add content -->
</Grid>
</StackPanel>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace Northwind
{
public partial class AdminPanel : Window
{
public string ServerName { get; set; }
public string ConnectionStatus { get; set; }
public AdminPanel(string name,string status)
{
InitializeComponent();
this.ServerName = name;
this.ConnectionStatus = status;
DispatcherTimer dtClockTime = new DispatcherTimer();
dtClockTime.Interval = new TimeSpan(0, 0, 1);
dtClockTime.Tick += dtClockTime_Tick;
dtClockTime.Start();
bindingData.DataContext = this;
}
private void dtClockTime_Tick(object sender, EventArgs e)
{
lblClock.Content = DateTime.Now.ToLongTimeString();
}
private void itmSchema_Click_1(object sender, RoutedEventArgs e)
{
//code
}
}
}

You can put the menu or whichever item you wanted to a conatainer (Grid) and you can use the Visibility attribute as collapsed when you want to hide that control.
if you are not using MVVM pattern then add it into the corresponding Event.
Container_Name.Visibility = Visibility.Collapsed;
Or else you can use the Xaml Triggers for obtain the same.
For that refer StackOverflow_Answer

Create a resources Dictionary and add this code
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Set names two different ways -->
<Button Name="okButton">OK</Button>
<Button x:Name="cancelButton">Cancel</Button>
<ListBox>
<!-- Set content three different ways -->
<ListBoxItem Content="Item 1" />
<ListBoxItem>Item 2</ListBoxItem>
<ListBoxItem>
<ListBoxItem.Content>Item 3</ListBoxItem.Content>
</ListBoxItem>
</ListBox> </StackPanel>
when option "Schemat bazy Northwind" was clicked
private void SchematbazyNorthwind_Click(object sender, RoutedEventArgs e)
{
StackPanel stackPanel = null;
using (FileStream fs =
new FileStream("Dictionary1.xaml", FileMode.Open, FileAccess.Read))
{
stackPanel = (StackPanel)XamlReader.Load(fs);
}
MainGrid.Children.Add(stackPanel);
}
Load your Resources and attach to you grid

Related

Getting and setting large amount of text data in clipboard

I'm making simple tool app in C#, I have a textbox and I want to paste some text (around 300k lines), but this makes the app unresponsive. I waited like 10 minutes and nothing moved forward.
Is there some way to handle paste and copy operations on large data sets in smoother way? For example pasting and copying same amount of data in Windows Notepad takes just few seconds.
I use
Windows.ApplicationModel.DataTransfer.Clipboard.GetContent()
and at this app hangs.
Example Code
Xaml
<Window
x:Class="App2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="1" Grid.RowSpan="1">
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" Grid.RowSpan="1" Margin="5" VerticalScrollBarVisibility="Visible" >
<TextBox VerticalAlignment="Stretch" HorizontalAlignment="Stretch" IsReadOnly="False" Header="Query Result" Text='{x:Bind pasteResult, Mode=TwoWay}' PlaceholderText="Paste results here" TextWrapping="Wrap"/>
</ScrollViewer>
</Grid>
</Window>
cs file
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace App2
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : Window
{
public string pasteResult;
public MainWindow()
{
this.InitializeComponent();
}
}
}
As #Simon Mourier mentioned in the comments, the performance problem is not related to the clipboard, but the TextBox control processing that amount of data.
So, let me give you another option using the ItemsRepeater which comes with virtualization built-in. (In my laptop) it takes approx. 3 secs to show 500K lines of text from the clipboard.
MainWindow.xaml
<Window
x:Class="ClipboardTests.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"
mc:Ignorable="d">
<Grid RowDefinitions="Auto,*">
<StackPanel
Grid.Row="0"
Orientation="Horizontal">
<Button
Click="PasteButton_Click"
Content="Paste" />
<Button
Click="ClearButton_Click"
Content="Clear" />
<TextBlock
x:Name="MessageTextBox"
VerticalAlignment="Center" />
</StackPanel>
<ScrollViewer Grid.Row="1">
<ItemsRepeater x:Name="TextItemsRepeaterControl" />
</ScrollViewer>
</Grid>
</Window>
MainWindow.xaml.cs
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel.DataTransfer;
namespace ClipboardTests;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private static async Task<IEnumerable<string>> GetTextLinesFromClipboard()
{
DataPackageView dataPackageView = Clipboard.GetContent();
if (dataPackageView.Contains(StandardDataFormats.Text) is true)
{
string text = await dataPackageView.GetTextAsync();
string[] lines = text
.ReplaceLineEndings()
.Split(Environment.NewLine, StringSplitOptions.None);
return lines;
}
return Enumerable.Empty<string>();
}
private async void PasteButton_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
IEnumerable<string> lines = await GetTextLinesFromClipboard();
this.TextItemsRepeaterControl.ItemsSource = lines;
stopwatch.Stop();
this.MessageTextBox.Text = $"Pasted {this.TextItemsRepeaterControl.ItemsSourceView.Count} items in {stopwatch.Elapsed.TotalSeconds} s.";
}
private void ClearButton_Click(object sender, RoutedEventArgs e)
{
this.TextItemsRepeaterControl.ItemsSource = null;
}
}

The Listbox items initialize but once one is selected I don't know how to RESET the listbox where neither item is selected

If you run the code neither of the list box items are selected. If you select either it remains selected okay and displays either "One" or "Two" in the text box accordingly. When the ResetListBox button is clicked the selected item is deselected (?maybe) but retains a grey background (undesired). Once the item has this light grey background the onClick event no longer fires... No additional text is added to the text box. This question has been asked in various forms all over the web and none of the answers that I've tried in this simple example has worked.
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="279.186" Width="401.98">
<Grid Margin="0,0,-6.8,-2.4">
<ListBox x:Name="ThanklessListBox" HorizontalAlignment="Left" Height="89" Margin="24,25,0,0" VerticalAlignment="Top" Width="117">
<ListBoxItem x:Name="ItemI" Content="ItemUno" Selected="ItemI_Selected"/>
<ListBoxItem x:Name="Item2" Content="ItemDos" Selected="Item2_Selected"/>
</ListBox>
<TextBox x:Name="StuffToShow" HorizontalAlignment="Left" Height="178" Margin="198,25,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="116"/>
<Button x:Name="ResetListBox" Content="ResetListBox" HorizontalAlignment="Left" Height="26" Margin="28,131,0,0" VerticalAlignment="Top" Width="116" Click="ResetListBox_Click"/>
<Button x:Name="SeleectButton" Content="SelectItemDos" HorizontalAlignment="Left" Height="24" Margin="28,179,0,0" VerticalAlignment="Top" Width="116" Click="SeleectButton_Click"/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ItemI_Selected(object sender, RoutedEventArgs e)
{
StuffToShow.Text += "\nOne";
}
private void Item2_Selected(object sender, RoutedEventArgs e)
{
StuffToShow.Text += "\nTwo";
}
private void SeleectButton_Click(object sender, RoutedEventArgs e)
{
ThanklessListBox.SelectedItem = 1; //Choose Dos
}
private void ResetListBox_Click(object sender, RoutedEventArgs e)
{
ThanklessListBox.SelectedItem = -1; //Deselect
}
}
In your code behind, you are using SelectedItem with an integer.
private void ResetListBox_Click(object sender, RoutedEventArgs e)
{
ThanklessListBox.SelectedItem = -1; //Deselect
}
Try either using SelectedIndex
private void ResetListBox_Click(object sender, RoutedEventArgs e)
{
ThanklessListBox.SelectedIndex = -1; //Deselect
}
Or Selected Item with null
Edited to add: You could also do
ThanklessListBox.SelectedItems.Clear
which is what I think the other person whose post is now deleted meant>
In fairness, this is something you could really learn just by browsing the online documentation for ListBox on the MS site

Looping through controls of different types and create stringbuilder for copy

I cannot find a similar question or answer to what I am asking but if there is one out there I do apologize. What I am looking to achieve is to simply loop through all my controls in a user control and then write to a string builder in the order of the loop.
The code below outlines a simple example and I did try some limited things based on me being knew to C# etc.
<Window x:Class="CopyandPaste.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:CopyandPaste"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid x:Name="MainGrid">
<Button x:Name="CopyButton" Content="Copy Button" HorizontalAlignment="Left" Margin="10,34,0,0" VerticalAlignment="Top" RenderTransformOrigin="-4.265,-0.098" Height="41" Width="120" Click="CopyButton_Click"/>
<Grid x:Name="MasterGrid" HorizontalAlignment="Left" Height="378" Margin="146,10,0,0" VerticalAlignment="Top" Width="572">
<TextBox HorizontalAlignment="Center" Margin="0,21,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="408" Height="23" Grid.Column="1"/>
<RadioButton x:Name="RadioButton" Content="RadioButton" HorizontalAlignment="Left" Margin="82,55,0,0" VerticalAlignment="Top" Height="15" Width="85"/>
<ComboBox x:Name="ComboBox" HorizontalAlignment="Left" Margin="82,95,0,0" VerticalAlignment="Top" Width="369" Height="22">
<ComboBoxItem>THIS IS IT 1</ComboBoxItem>
<ComboBoxItem>THIS IS IT 2</ComboBoxItem>
<ComboBoxItem>THIS IS IT 3</ComboBoxItem>
</ComboBox>
<TextBox HorizontalAlignment="Center" Margin="0,140,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="408" Height="23"/>
<TextBox HorizontalAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Center" Width="408" RenderTransformOrigin="0.333,0.782" Height="28"/>
</Grid>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace CopyandPaste
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void CopyButton_Click(object sender, RoutedEventArgs e)
{
bool first = true;
StringBuilder value = new StringBuilder();
// some loop here
Clipboard.SetText(value.ToString());
}
}
}
I played with some foreach loops that were nested but of course got some results that were unwanted:
1
RadioButton
THIS IS IT 2
2
RadioButton
THIS IS IT 2
3
RadioButton
THIS IS IT 2
I understand what is going on here where the text box is causing the loop to continue. Trying to get some ideas or a point in the right direction to achieve an output like:
1
RadioButton
THIS IS IT 2
2
3
Any WPF UIElement has a Children property which will give you all its child controls. So you can use it to enumerate children and then loop through them. Something like;
foreach (var child in MasterGrid.Children)
{
// string builder code
}
You haven't quite clearly explained what your criteria to display a control is, but from the limited explanation you've given it looks like you want to grab the Text property if it's a TextBox, the SelectedItem if it is a ComboBox, and the name of the control if it is a RadioButton. These are quite different things when it comes to a control, so inside the loop you will have to check the type of each child control and get the right information.
private void CopyButton_Click(object sender, RoutedEventArgs e)
{
StringBuilder value = new StringBuilder();
foreach (var child in MasterGrid.Children.OfType<Control>())
{
if (child.GetType() == typeof(TextBox))
{
value.Append(((TextBox)child).Text + Environment.NewLine);
}
else if (child.GetType() == typeof(RadioButton))
{
var rb = (RadioButton)child;
if (rb.IsChecked == true)
{
value.Append(rb.Name + Environment.NewLine);
}
}
else if (child.GetType() == typeof(ComboBox))
{
value.Append(((ComboBox)child).Text + Environment.NewLine);
}
}
MessageBox.Show(value.ToString());
}
Above is for the types of controls you've mentioned, you'll have to figure out what to do with other control types.
Next when it comes to order, there can be different meanings to the 'order' in controls in a WPF GUI.
Do you want the order in which they are listed in the XAML code?
Or do you want the order in which they are displayed when the program is run?
For example, in your code if you moved the last text box XAML code above the second-to-last, they will still appear in the original order when you run the program because you've hard-coded their locations (which in itself is a bad idea; you should use grids, stack panels etc to do your layout in WPF).
I think if you care about the order, best option is to modify your XAML and specify the Tag property of each control.
E.g.
<TextBox Grid.Column="1"
Width="408"
Height="23"
Margin="0,21,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Tag="1"
TextWrapping="Wrap"/>
Then when you enumerate the children, do an order by tag before iterating through them.
This way you have firm control over whatever it is that you mean by order.
var children = MasterGrid.Children.OfType<Control>().OrderBy(x => x.Tag);
foreach (var child in children)
{
// Same as before
}

Selection text disappears when contextmenu pops up

Greetings All,
I am getting some weird behavior from WPF when i create an event to programatically open a context menu. once I select a text and right click the highlight of the selection disappears once the context menu opens up.
Here is a sample of the problem:
Xaml:
<Window x:Class="WpfApplication19.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="287" Width="419">
<Grid>
<TextBox x:Name="myText" Height="38" Margin="0,72,6,0" VerticalAlignment="Top" HorizontalAlignment="Right" Width="135"></TextBox>
<Label Height="30" Margin="12,80,164,0" Name="label1" VerticalAlignment="Top">Textbox with contextMenu property set</Label>
<Label Height="30" Margin="12,0,136,91" Name="label2" VerticalAlignment="Bottom">TextBox Open ContextMenu by programmatically</Label>
<TextBox Height="38" Margin="0,0,6,81" x:Name="myText1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="135" />
</Grid>
</Window>
And the Code Behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication19
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
ContextMenu con = new ContextMenu();
public Window1()
{
InitializeComponent();
MenuItem menuItem1 = new MenuItem();
menuItem1.Header = "Menu1";
MenuItem menuItem2 = new MenuItem();
menuItem2.Header = "Menu2";
con.Items.Add(menuItem1);
con.Items.Add(menuItem2);
this.myText.ContextMenu = con;
this.myText1.PreviewMouseDown += new MouseButtonEventHandler(myText1_PreviewMouseDown);
}
void myText1_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
base.OnPreviewMouseDown(e);
if (e.RightButton == MouseButtonState.Pressed)
{
con.Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint;
con.IsOpen = true;
IInputElement focusedElement = FocusManager.GetFocusedElement(this);
}
}
}
}
Thank you in advance!
Note:
I found out that adding con.focusable = false tends to work with the solution. but can anybody explain why is that?
Whenever u open the contextmenu the focus will go to the contextmenu,
and that's why the textbox will hide the selection.
A Simple solution for this problem is:
Change contextmenu property Focusable to false
<ContextMenu Focusable="False">
And change Focusable for every item to false
<MenuItem Command="Copy" Focusable="False">
Simple example:
<TextBox Text="Right-click here for context menu!">
<TextBox.ContextMenu>
<ContextMenu Focusable="False">
<MenuItem Command="Cut" Focusable="False"/>
<MenuItem Command="Copy" Focusable="False"/>
<MenuItem Command="Paste" Focusable="False"/>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
This way the focus will stay on the textbox,
and the highlight will remain Visible
I can get you started, but this solution has some serious usability issues that you may need to overcome.
Add a LostFocus event handler to control myText.
Set myText1.Focus() during the right-click event so that you can trigger the LostFocus event.
This solution means that myText stays selected when you might want to unselect its value.
Also take at look at this answer for more information.
<TextBox LostFocus="myText_LostFocus" x:Name="myText" Height="38" Margin="0,72,6,0" VerticalAlignment="Top" HorizontalAlignment="Right" Width="135"></TextBox>
void myText1_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
base.OnPreviewMouseDown(e);
if (e.RightButton == MouseButtonState.Pressed)
{
// added next line
myText1.Focus();
con.Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint;
con.IsOpen = true;
IInputElement focusedElement = FocusManager.GetFocusedElement(this);
}
}
private void myText_LostFocus(object sender, RoutedEventArgs e)
{
e.Handled = true;
}
Simply,
Properties > Hide Selection = False
Its nice to hear, you got the right answer. but simply this will also works
Thanks

How do I get rid of the red rectangle when my wpf binding validation has failed and the containing panel is no longer visible?

I have a situation where I am using wpf data binding and validation using the ExceptionValidationRule.
Another part of the solution invovles collapsing some panels and showing others.
If a validation exception is set - i.e. the UI is showing a red border around the UI element with the validation problem, and the containing panel is collapsed, the red border is still displayed. This is clearly not meant to be? Is there a workaround for this? Anyone know if this is by design?
Minimal code example provided (not my actual code, but replicates the problem). Create a new WpfApplication (I called mine WpfDataBindingProblem).
The xaml for window1 is as follows:
<Window x:Class="WpfDataBindingProblem.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel Margin="5">
<StackPanel Name="panel1" Visibility="Visible" Margin="5">
<TextBox Name="DataBoundTextBox">
<Binding Path="TextValue">
<Binding.ValidationRules>
<ExceptionValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox>
</StackPanel>
<StackPanel Name="panel2" Visibility="Collapsed" Margin="5">
<TextBlock>
The quick brown fox jumps over the lazy dog.
</TextBlock>
</StackPanel>
<Button Click="Button_Click" Margin="5">
Toggle panels
</Button>
</StackPanel>
</Window>
The code for window1 is as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfDataBindingProblem {
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
this.DataContext = new MyClass("default");
}
private void Button_Click(object sender, RoutedEventArgs e) {
panel1.Visibility = panel1.Visibility == Visibility.Collapsed ?
Visibility.Visible : Visibility.Collapsed;
panel2.Visibility = panel2.Visibility == Visibility.Collapsed ?
Visibility.Visible : Visibility.Collapsed;
}
}
public class MyClass : INotifyPropertyChanged {
private string mTextValue;
public MyClass(string defaultText) {
TextValue = defaultText;
}
public string TextValue {
get {
return mTextValue;
}
set {
mTextValue = value;
if (string.IsNullOrEmpty(mTextValue)) {
throw new ApplicationException("Text value cannot be empty");
}
OnPropertyChanged(new PropertyChangedEventArgs("TextValue"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) {
if (this.PropertyChanged != null) {
this.PropertyChanged(this, e);
}
}
}
}
To reproduce the problem, run the application. Delete the default text from the textbox and tab off - red rectangle is shown indicating a validation problem. Click the button. Panel containing control with red rectangle is hidden and another panel is shown, but the red rectangle remains. Aargh!
All help much appreciated.
PS apologies for long question title!
If I remember correctly, this is a known issue. We re-templated textbox to include the following:
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<ControlTemplate.Resources>
<BooleanToVisibilityConverter x:Key="converter" />
</ControlTemplate.Resources>
<DockPanel LastChildFill="True">
<Border
BorderThickness="1"
BorderBrush="Red"
Visibility="{Binding ElementName=placeholder, Mode=OneWay, Path=AdornedElement.IsVisible, Converter={StaticResource converter}}">
<AdornedElementPlaceholder x:Name="placeholder" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
I have an answer to the problem myself which is to change my button click event which changes the visibility of the panels. This would change to something like this:
private void Button_Click(object sender, RoutedEventArgs e) {
if (panel1.Visibility == Visibility.Collapsed) {
panel1.Visibility = Visibility.Visible;
DataBoundTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
panel2.Visibility = Visibility.Collapsed;
}
else {
panel1.Visibility = Visibility.Collapsed;
DataBoundTextBox.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
panel2.Visibility = Visibility.Visible;
}
}
The UpdateSource() and UpdateTarget() have the effect of reapplying and removing the red rectangle, but this seems like an ugly hack. Surely the wpf framework should be hiding the red rectangle for me when the containing panel is collapsed. Any cleaner fix that doesn't require me to fiddle with the binding expression gets my vote.
Thanks,
Sam

Categories