Wpf - Update custom user control at runtime - c#

hey guys i created an wpf application with custom usercontrol , the problem is that i am unable to update or say change the properties of custom control at runtime,
-->heres a code of user control
File:usercontrol.xaml
<UserControl x:Class="ExampleWpf.UserControlExample"
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"
Width="Auto" Height="Auto">
<Rectangle Width="60" Height="200" Fill="#FFB65959" Name="Box1"></Rectangle>
File:usercontrol.xaml.cs
namespace ExampleWpf{
public partial class UserControlExample : UserControl
{
public UserControlExample()
{
InitializeComponent();
}
public double Box1Width
{
get { return (Box1.Width); }
set { Box1.Width = value; }
}
}
--->Heres a simple code for Wpfapplication
File:Mainwindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
UserControlExample uc = new UserControlExample();
uc.Box1Width = 100;
}
}
All i wanted to change the width of a custom control at run time when user click on the button----> but unfortunately i doesn't succeed
please help me out

You are creating a new instance of your user control and not doing anything with it.
You should be finding the existing instance of the control and updating that.
You should also implement this type of behaviour through Dependency Properites. This gives you all sorts of advantages as outlined in the tutorial, but the main one here is that you can set properties like this at runtime.

Use the instance you placed in your xaml (f.e. through the Name-property), like so:
<uc:UserControlExample Name="myUC"/>
myUC.Box1Width = 100;
Another problem could be, that you just change the Width of the UC's rectangle. So lets say the UC has a Width of 50 and you call uc.Box1Width=100, the UC has still a Width of 50, so it wont change anything.

Related

WPF view who leads to another using MVVM

I am trying to set up a navigation between views using a MVVM pattern. My application contains a MainWindow and two views with a button each. When I click on the button in the View1 I want to set up the View2 on the MainWindow.
I have found several tutorials witch explain how to navigate from a view to another with a button on the main window (simulate a tabControl), it works but it is not what I want.
I'm looking for something like :
View1_View.xaml.cs :
public partial class View1_View : UserControl
{
private View1_ViewModel _viewModel = new View1_ViewModel();
public View1_View()
{
InitializeComponent();
}
private void Btn_SwitchToView2_Click(object sender, RoutedEventArgs e)
{
MainWindow.SwitchToView2();
}
}
MainWindow.xaml.cs :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new View1_View();
}
public void SwitchToView2()
{
this.DataContext = new View2_View();
}
}
My problem is if I do so, from the class View1_View I cannot access to the method SwitchToView2() if it is not static, and if it is static I lose the context of the MainWindow.
How should I proceed ?
Thanks.
I would recommend using a ContentControl to switch the part of your main view.
This could look like this (short form just to give you an idea; without INotifyPropertyChanged).
Create an empty interface of type ISwitchableViewModel.
Add a property to your main ViewModel
public property ISwitchableViewModel MyViewModel {get; set;}
Create two classes that implements the interface ISwitchableViewModel. Each for each view you want to show (View1 and View2 in your example) and call them ViewModel1 and ViewModel2.
When you press the button in your xaml set the MyViewModel to View1 or View2; whatever your logic is.
In your xaml add this at the place where you want to show the switchable content.
<ContentControl Content="{Binding MyViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewModel:ViewModel1}">
<view:View1 />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:ViewModel2}">
<view:View2 />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
When you set the MyViewModel in your MainViewModelthe UI will show automatically the correct view for that viewmodel.
You can achieve this by creating the views and assigning them to a content control.
Lets assume you have this content control in your main view.
<Window x:Class="MVVM.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:MVVM"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button x:Name="ChangeView" Click="SwitchToSecondView" Content="Set View"></Button>
<ContentControl x:Name="MainContent"></ContentControl>
</StackPanel>
</Window>
You can then set the content in the code behind file of your main view.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void SwitchToSecondView(object sender, outedEventArgs e)
{
var view = new SecondView();
var model = new SecondViewModel(this);
view.DataContext = model;
MainContent.Content = view;
}
public void SwitchToThirdView(object sender, outedEventArgs e)
{
var view = new ThirdView();
var model = new ThirdViewModel(this);
view.DataContext = model;
MainContent.Content = view;
}
}
Another solution would be to use an MVVM Framework light Caliburn.Micro, Prism etc, which essential do the same thing as the code snippet above, but hide the boilerplate code.
EDIT: I realized i didn't explicitly get to the second part of your question.
Usally one would need some kind of router which is able to control the navigation. For the sake of simplicity we use the main view as router. To access the main view, you need to inject it in each component.
This allows each of your submodels to access the main view.
This could be improved by using some kind of DI-Container or by a Mediator. A mediator would allow each component to send requests, which then are dispatched to the MainView, eliminating the direct dependency.

How to remove child from UserControl?

It's maybe stupid question but I just cant figure out how to do this. I have a UserControl named "ReportUserControl" and inside of it I have a Grid named "ReportGrid" . I want to remove ReportGrid from ReportUserControl children. I tried this:
ReportUserControl control = new ReportUserControl();
control.Children.Remove(...);
Problem is that there is no .Children.Remove() option for UserControl. How can i manage this?
UPDATE
I want to "send" this grid to another class and use it there. Problem is that when I send the grid and try to use it in another class I get this: "Specified element is already the logical child of another element. Disconnect it first." so I have to remove it from my UserControl but don't know how.
ReportUserControl XAML:
<UserControl x:Class="WPFReportTest.ReportUserControl "
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"
Height="29.7cm" Width="21cm">
<Grid Name="ReportGrid">
****GridContent***
</Grid>
</UserControl>
ReportClass:
public class ReportingClass
{
Grid reportGrid;
public ReportingClass(Grid tempGrid)
{
reportGrid = tempGrid;
}
public Page SetPageContent()
{
Page page = new Page();
page.Content = reportGrid;
}
}
InstanceWindow
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ReportUserControl userControl = new ReportUserControl();
ReportingClass report = new ReportingClass(userControl.ReportGrid);
}
}
This is not the full code only small part to get idea of what I'm trying to manage. Line "page.Content = reportGrid;" is where the exception happens.
UserControl has Content property (msdn).
If you want to delete content set null:
ReportUserControl control = new ReportUserControl();
control.Content = null;
Example:
public class ReportingClass
{
ReportUserControl _reportUserControl;
Grid reportGrid;
public ReportingClass(ReportUserControl reportUserControl, Grid tempGrid)
{
_reportUserControl = reportUserControl;
reportGrid = tempGrid;
}
public Page SetPageContent()
{
Page page = new Page();
// _reportUserControl is the instance of object ReportUserControl where content is reportGrid
_reportUserControl.Content = null;
page.Content = reportGrid;
return page;
}
}
Edit:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ReportUserControl userControl = new ReportUserControl();
ReportingClass report = new ReportingClass(userControl, userControl.Content as Grid);
}
}
#Stojdza, in WPF, we generally don't manipulate UI elements in that way. We manipulate data elements and declare DataTemplates to define what that data looks like. So in your case, you shouldn't literally move the Grid... just move the data and let WPF regenerate your Grid wherever you want using the same DataTemplate. It will look just the same, but it's a whole lot easier and you'll save yourself all of this trouble.
So, if you're interested in doing this the easy way, first create a class that has all of the properties required in the UserControl, let's call it Data. Now declare a DataTemplate in Application.Resources so that you can use it application-wide:
<DataTemplate DataType="{x:Type YourNamespacePrefix:Data}">
<!-- Define your Grid here -->
<DataTemplate>
Now whenever you want to display your Grid, whether in a collection, or individually, you just need to bind a property of type Data (or a collection of them) to some form of ContentControl:
public Data Data { get; set; }
...
<ContentControl Content="{Binding Data}" />
So rather than trying to copy the Grid, you just copy the data to another property of type Data and recreate the Grid with the single line above. See the Data Templating Overview page on MSDN for more information on DataTemplates.

How to access WPF MainWindow Controls from my own .cs file

I am a NOVICE and am very much struggling with what seems should be a very simple task. How do I modify a property of a MainWindow TextBlock, from another cs file. An exact code solution would be extremely helpful.
Below is the stripped down code. Is my use of static class causing me extra issues?
In File: MainWindow.xaml
<Window x:Class="TestApp1.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>
<TextBlock x:Name="TextBlock1" HorizontalAlignment="Left" Margin="107,71,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top"/>
</Grid>
</Window>
In File: MainWindow.xaml.cs
namespace TestApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TextBlock1.Text = "Setting Text from MainWindow";
MyProgram.myProgramStart();
}
}
}
In File: CodeFile1.cs
namespace TestApp1
{
public static class MyProgram
{
public static void myProgramStart()
{
// ... blah blah blah
// I want to do something like follows, but won't compile
MainWindow.TextBlock1.Text = "Setting Text from My Program";
}
}
}
Because nobody else has actually answered the question I'm going to tell you how to achieve what you want, but do listen to the posters who said that in a real application you would use MVVM. However there are times when you need to do what you ask so the code you need is:
((MainWindow)System.Windows.Application.Current.MainWindow).TextBlock1.Text = "Setting Text from My Program";
You can simply achieve this using MVVM. You shouldn't directly access controls in View using its name from another class. You have to use binding properties.
First of all, add a class. This will be your ViewModel. Add your properties to this class which will be binded to your input controls in your View.
Student ViewModel
public class Student
{
public string Name
{
get { return "Setting Text from My Program"; }
}
}
App.Config
Now you have add to this View Model as a resource in your App.Config file. First, add the name space reference to your app.config where your VM resides.
[xmlns:local="clr-namespace:WpfApplication2]. Add your VM class as a resource by specifying your View Model class name (student).
<Application x:Class="WpfApplication2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
xmlns:local="clr-namespace:WpfApplication2">
<Application.Resources>
<local:Student x:Key="Student" />
</Application.Resources>
</Application>
MainWindow.xaml
Set the DataContext with the resource key added to App.config and set the binding to the property defined in the Student View Model.
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{StaticResource Student}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock Text="{Binding Name}" Height="23" HorizontalAlignment="Left" Margin="127,124,0,0" Name="textBlock1" VerticalAlignment="Top" Width="214" />
</Grid>
</Window>
Basically there are more than 2-3 methods. Given method is quite easier to understand & handle.
You can access MainWindow controls by following codes (1),(2),(3),(4).
In File: MainWindow.xaml.cs
public partial class MainWindow
{
internal static MainWindow Main; //(1) Declare object as static
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Main =this; //(2) Defined Main (IMP)
var AnyClassORWindow=new Class1(); //Initialize another Class
AnyClassORWindow.ShowValue();
}
}
In File: Class1.cs
internal class Class1 : MainWindow //(3) Inherited
{
internal void Display()
{
MessageBox.Show(Main.TextBox1.Text); //(4) Access MainWindow Controls by adding 'Main' before it.
}
}
Notes:-
It's good practice to use code (2) after window LOADED not in CONSTRUCTOR.
Code (2) in constructor may leave run-time problems.
Another simple method is to use 'ref MainWindow_field' by passing to each class's Constructor OR assign '(MainWindow) Application.Current.MainWindow' to static Main.
Use MVVM pattern to access properties of the control and modify them:
public class Student
{
public Student()
{
}
public string Name
{
get { return "Setting Text from My Program"; }
}
}
Set the DataContext of the XAML in the code behind:
this.DataContext = new Student();
Bind the Text property to Name:
<TextBlock Text="{Binding Name}"/>
As for why it won't compile, I will assume the compiler error you are getting is...
An object reference is required for the non-static field, method, or property 'TestApp1.MainWindow.TextBlock1'
This happens because you are trying to access an TextBlock1 as if it were static. As #JeffRSon stated, create an instance of your MainWindow class first.
// Create an instance of MainWindow
var mainWindow = new MainWindow();
mainWindow.TextBlock1.Text = "Setting Text from My Program";
I assume you may want to display the window as well...
mainWindow.ShowDialog();
You need to create an instance of MainWindow.
But there shouldn't be a reason to do that because it will be done automagically in an WPF app. Unless you have specific reason to do that (which I doubt because of this question and because you say you're a novice).
To extend on what Nathan said, I used a safe cast:
(System.Windows.Application.Current.MainWindow as MainWindow)?.TextBlock1.Text = "Setting Text from My Program";
Note the comments on the answer Nathan gave. This isn't ideal but it works.
you can use App.Current.MainWindow anywhere in your app :)
You should also use the FindChild method to find your component ...
MyProgram.cs :
public static class MyProgram
{
public static void myProgramStart()
{
// ... blah blah blah
// I want to do something like follows, but won't compile
//MainWindow.TextBlock1.Text = "Setting Text from My Program";
var tb = FindChild3<TextBlock>(App.Current.MainWindow, "TextBlock1");
tb.Text = "Setting Text from My Program";
}
public static T FindChild3<T>(DependencyObject depObj, string childName)
where T : DependencyObject
{
// Confirm obj is valid.
if (depObj == null) return null;
// success case
if (depObj is T && ((FrameworkElement)depObj).Name == childName)
return depObj as T;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
//DFS
var obj = FindChild3<T>(child, childName);
if (obj != null)
return obj;
}
return null;
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
MyProgram.myProgramStart();
}
}
This trick worked for me :)
GoodLuck ✌️

WPF Custom Control Error

I have some problems with a WPF custom control, I'm trying to make it work but just don't get it:
Here is my problem, I'm creating a simple custom control that's almost the same to a TextBox. This control has a dependency property named "Texto", and the binding between the XAML and back-code of the custom control works fine, here is the code:
<UserControl x:Class="WpfCustomControlLibrary1.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="47" d:DesignWidth="147">
<Grid Height="43" Width="142">
<TextBox Height="23" HorizontalAlignment="Left" Margin="12,8,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Texto}"/>
</Grid>
And the dependency property code:
public static readonly DependencyProperty TextoProperty = DependencyProperty.Register("Texto", typeof(string), typeof(UserControl1));
public string Texto
{
get
{
return (string)GetValue(TextoProperty);
}
set
{
SetValue(TextoProperty, value);
}
}
Ok, now the problem: When I use this control in other windows I try to bind the "Texto" property to a viewmodel (as simple as everything else) but the property on the view model just dont change:
The ViewModel code:
public class ViewModelTest
{
public string SomeText { get; set; }
}
And the code of the applicatoin Window:
public ViewModelTest test;
public Window1()
{
InitializeComponent();
}
private void button1_Click_1(object sender, RoutedEventArgs e)
{
MessageBox.Show(test.SomeText);
MessageBox.Show(uc.Texto);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
test = new ViewModelTest();
this.DataContext = test;
}
And the binding with the property of the view model:
<my:UserControl1 HorizontalAlignment="Left" Margin="27,12,0,0" Name="uc" VerticalAlignment="Top" Texto="{Binding Path=SomeText,Mode=TwoWay}"/>
Just for make it clearer, if I write "Hello" in the custom control and then I push the "button1", the first message shows nothing and the second message shows "Hello".
As you can see I'm fairly new into this, so I hope some of you can help me. Thanks.
Your binding Texto="{Binding SomeText}" works fine, the problem is the rebinding from your user control to the inner textbox. Remember binding will ALWAYS, if not modified, refere to the DataContext. But your DataContext doesn't contain the property Texto. Your control has that, To refere to that you need something called TemplateBinding, but this only works when you are in a ControlTemplate. Which you aren't so what is the solution?
You can use a special form of binding, by changing the source from the DataContext to a control with a given name: First give your UserControl a name
<UserControl x:Class="WpfCustomControlLibrary1.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"
x:Name="mainControl"
d:DesignHeight="47" d:DesignWidth="147">
and now change the binding to refere to the control, not the DataContext of the control anymore.
<TextBox Text="{Binding ElementName=mainControl, Path=Texto}"/>
Now your ViewModel binds to your user control and the content of the user control binds to the user controls Texto property.
Also one minor thing, what you called custom control, is in fact a user control, custom controls are something else.

WPF PropertyGrid doesn't apply property value

I'm using this
property control for WPF from Denis Vuyka.
I have the problem that it doesn't apply the new value of a property, if I don't press the TAB key.
So if I change a property in the property grid and then click the OK button, the property has still the previous value.
Sample code to reproduce:
public partial class MainWindow : Window
{
DataObject dataObject = new DataObject();
public MainWindow()
{
InitializeComponent();
propertyGrid.SelectedObject = dataObject;
}
private void OnOK(object sender, RoutedEventArgs e)
{
MessageBox.Show("Value of test is " + dataObject.test);
}
}
class DataObject
{
public int test { get; set; }
public int test2 { get; set; }
}
<Window x:Class="PropGridTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pg="http://schemas.denisvuyka.wordpress.com/wpfpropertygrid"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Grid.Row="0" IsDefault="True" Click="OnOK">OK</Button>
<pg:PropertyGrid x:Name="propertyGrid" Grid.Row="1">
</pg:PropertyGrid>
</Grid>
</Window>
Just type a number into property test and then click the OK button.
Does anybody know a workaround for this problem?
This is what I tried in OnOK so far to no avail:
propertyGrid.Focus();
propertyGrid.MoveFocus(new System.Windows.Input.TraversalRequest(System.Windows.Input.FocusNavigationDirection.Next));
System.Windows.Forms.SendKeys.SendWait("{TAB}");
You'd need to edit the source code and change the binding on the text editor so that it uses UpdateSourceTrigger=PropertyChanged.
To find the area of source that needs updating you can use Snoop to inspect the control.
Get your application running, fire up snoop, pick your application from the drop down menu on the Snoop tool, and click the binoculars. Now if you hold shift and ctrl keys while you hover the cursor over the control you'll be able to see its type and all of its properties.
After that you just need to search the solution to find that type and edit the binding in the XAML. Take a look at this page for info on how to use the UpdateSourceTrigger binding property.
I do not know exactly for this grid (I use this one), but I have the same problem there. It seems to be a common problem. Try to remove focus from PropertyGrid to another control before selecting new object ot clearing selected object property. For example:
public static void UpdatePropertyGridObjects(object objToSelect)
{
Components.DockManager.Focus();
Components.PropertyGrid.SelectedObject = objToSelect;
}

Categories