I have a XAML layout similar to this:
<Grid>
<TextBox x:Name="inputTextBox" LostFocus="inputTextBox_LostFocus" TextChanged="inputTextBox_TextChanged" GotFocus="inputTextBox_GotFocus" />
<ComboBox x:Name="inputComboBox" SelectionChanged="inputComboBox_SelectionChanged">
<ComboBoxItem IsSelected="True">10</ComboBoxItem>
<ComboBoxItem>15</ComboBoxItem>
<ComboBoxItem>20</ComboBoxItem>
</ComboBox>
<ComboBox x:Name="inputComboBoxTwo" SelectionChanged="inputComboBoxTwo_SelectionChanged">
<ComboBoxItem IsSelected="True">1</ComboBoxItem>
<ComboBoxItem>2</ComboBoxItem>
<ComboBoxItem>3</ComboBoxItem>
</ComboBox>
</Grid>
Pretty simple. In the codebehind C# file, I use these controls to take in a double from the TextBox, some more ints from the ComboBoxes, then I create a calculator type object with the data from the controls. I make the calculation and display the results in some other TextBlocks.
namespace TipCalc
{
public sealed partial class MainPage : Page
{
Calc x = new Calc();
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
}
//
//Appropriate event handlers from XAML controls that all call the calculation method.
//
private void calcIt()
{
x.amt = double.Parse(inputTextBox.Text);
x.cal1 = int.Parse(inputComboBox.SelectedItem.ToString());
x.cal2 = int.Parse(inputComboBoxTwo.SelectedItem.ToString());
//Send calculated values to output TextBlocks.
}
}
}
When I run this program, I hit a null pointer exception that is thrown when I attempt to access the text property of the TextBox. It turns out that all of the XAML controls are null. However, _contentLoaded is set to true and the code definition for this.IntializeComponent looks correct behind the scenes.
Why are all my controls set to null when it seems like everything is working correctly? Is there a way to manually initialize them if they aren't correctly being initialized automatically? Am I doing anything wrong?
the code run like:
Calc x = new Calc();
this.InitializeComponent();
Calc() was first than InitializeComponent(), but InitializeComponent() create your controls.
you can change to:
Calc x;
public MainPage()
{
this.InitializeComponent();
x = new Calc();
this.NavigationCacheMode = NavigationCacheMode.Required;
}
I have the same problem with some of my TextBox controls when the class initializes. What I did to solve this is not the real and perfect solution because not all the controls (TextBox, ComboBox, RadioButton, etc) are null when the class is running, and there's something happening in my code or my app or my VS that I'm missing or doing wrong.... But at least is working fine now. I hope is useful to you:
if(TextBox1 == null)
{
//I'm re-initializing the control because is null
TextBox1 = new TextBox();
}
For your code it should be something like this:
if(inputTextBox == null)
{
inputTextBox.Text = new TextBox();
}
x.amt = double.Parse(inputTextBox.Text);
I hope this 'solution' is good enough for you. And for my poor English I apologize if I have mistakes, is not my native language.
Related
What I'm trying to do is update a GridView on a page. In my page behind code I have a method that returns a DataTable from a query from a database. I then want to set the DataContext of the DataGrid to the returned DataTable. If the GridView is on the main window of the application I can run the code and set the DataContext, but if I try to do the same from even the behind code of the page the GridView doesn't change. I've set breakpoints and I can verify that the DataTable does contain valid data. Why will the GridView work in the main window, but not in the page?
I'm not sure what code to include, but here's what I can think of to include.
Code that returns the DataTable is in the items.xaml.cs file.
public partial class EAHQ_Items : Page
{
public static DataTable ReadAll(string keyword)
{
CRUD.sql = "SELECT id, name " +
"FROM eahqItems " +
"WHERE name LIKE #keyword1 OR #keyword2 ORDER BY id ASC";
string strKeyword = string.Format("%{0}%", keyword);
CRUD.cmd = new MySqlCommand(CRUD.sql, CRUD.con);
CRUD.cmd.Parameters.Clear();
CRUD.cmd.Parameters.AddWithValue("keyword1", strKeyword);
CRUD.cmd.Parameters.AddWithValue("keyword2", keyword);
DataTable dt = CRUD.PerformCRUD(CRUD.cmd);
return dt;
}
}
I have an items.xaml file with a corresponding items.xaml.cs file. In the main window there is a frame that displays the items.xaml file. The items.xaml file contains the following controls.
<Grid>
<DataGrid x:Name="EAHQ_DG_Items" Margin="0,50,0,0" ItemsSource="{Binding}"/>
<Label Content="EAHQ Items" HorizontalAlignment="Center" Margin="0,20,0,0" VerticalAlignment="Top"/>
</Grid>
The main windows have the following controls.
<DataGrid x:Name="dgItems" Grid.Column="2" Margin="10,120,10,0" Grid.Row="1" VerticalAlignment="Top" Height="100" ItemsSource="{Binding}"/>
The main windows behind code has the following line when the application loads. It's loading the DataContext of the control in the main window.
dtItems.DataContext = items.ReadAll("");
This code is what is in the behind code of the page.
public static void LoadData(string keyword)
{
EAHQ_Items p = new EAHQ_Items();
p.EAHQ_DG_Items.DataContext = ReadAll("");
}
As you can see, both lines run the same code, but the page DataGrid doesn't update. Sorry for the long post, but it's the best way I could think to ask.
Thank you!
As you can see, both lines run the same code
No, they certainly don't. You are creating a new instance of EAHQ_Items in LoadData and then set the DataContext of this one.
Your setup is a bit unclear but you should just be able to set the DataContext of the control in the code-behind of the page, just like you do in the window:
public partial class Items : Page
{
public Items()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
EAHQ_DG_Items.DataContext = ReadAll("");
}
}
I'm a bit new to WPF/XAML (though I've learnt C#) and would really appreciate any help for my question. I did look around other posts and google for a while but I can't seem to find a satisfactory or detailed answer to get me going on with my project. Please look below for details. Thanks you in advance!
Objective
I have a class called Tile that consists of a few properties and an event handler.
I also have an ItemControl that has a button (as by the DataTemplate), and whose ItemSource is a collection of Tiles.
Now, I want to bind the "Click" event of the Button so as to invoke the Event Handler method defined in the class Tile.
In other words when I click the button of any item in the ItemControl, the method handler of the corresponding Tile instance (from the collection) must be invoked. How would I tackle this problem?
Below is the entire code, simplified to avoid distraction:
XAML
<Window x:Class="SampleWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<!-- Make a ItemControl for "Tile"s. -->
<ItemsControl x:Name="TileList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Wire the click event of this Button
to event handler in the Tile class. -->
<Button Content="Show"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
CODE-BEHIND
namespace SampleWPF
{
public partial class MainWindow : Window
{
ObservableCollection<Tile> tiles;
public MainWindow()
{
InitializeComponent();
// Adding some sample data for testing.
tiles = new ObservableCollection<Tile>();
tiles.Add(new Tile("Item 1"));
tiles.Add(new Tile("Item 2"));
TileList.ItemsSource = tiles;
}
}
public class Tile : INotifyPropertyChanged
{
public string Data
{ /* Accessors and PropertyNotifiers */ }
public Tile(string data)
{ /* Initializing and assigning "Data" */ }
// INotifyPropertyChanged implementation...
// { ... }
// This event handler should be bound to the Button's "Click" event
// in the DataTemplate of the Item.
public void ShowButton_Click(object sender, EventArgs e)
{
MessageBox.Show("Viewing item from: " + this.Data);
}
}
}
Hence, if I click the first "Show" button, the output should be "Viewing item from: Item 1" and if I click the second "Show" Button, the output should be "Viewing item from: Item 2".
So what is the recommended/efficient way to do this? Is my code inappropriate for this requirement?
Event handlers are the wrong approach - use Commands and more importantly MVVM.
As I can see that you are new (and probably from a WinForms or ASP.NET background) you should read this blog to understand how your thinking needs to change - this is the most important part to understand before tackling WPF: http://rachel53461.wordpress.com/2012/10/12/switching-from-winforms-to-wpfmvvm/
You should also read Kent Boogart's blog on how MVVM works from base principles: http://kentb.blogspot.co.uk/2009/03/view-models-pocos-versus.html
Let me start with some basics:
Don't assign itemsource in codeBehind - use Binding like this:
<Controll ItemSource="{Binding MyObservableCollection}"/>
There are many ways You can achieve this. I think that using this.Data is not the best solution for this.
For example if Your tail have ID or something You can assign this id to button CommandParameter like below
<Button CommanParameter="{Binding Path=ID}" Click="ShowButton_Click"/>
And then in Your button_click event u can 'catch' this like this:
public void ShowButton_Click(object sender, EventArgs e)
{
int ID = int.Parse(((Button)sender).CommandParameter.ToString());
}
EDIT
To use this binding You need to set DataContext. You can do this in ctor like this:
public MainWindow()
{
InitializeComponent();
// Adding some sample data for testing.
tiles = new ObservableCollection<Tile>();
tiles.Add(new Tile("Item 1"));
tiles.Add(new Tile("Item 2"));
// below You are setting a datacontext of a MainWindow to itself
this.DataContext = this;
}
ANOTHER EDIT
Let's assume Your tail class have property called ID. If You bound this ID to Button.CommandParameter You can later retrieve the tile with linq like this:
public void ShowButton_click(object sender, EventArgs e)
{
int MyId = int.Parse(((Button)sender).CommandParameter.ToString());
Tile TileIWasSearchingFor = (from t in tiles where t.ID == MyId select t).First();
// do something with tile You found
}
Well since my requirement was rather "simple", I've managed a work around, avoiding commands. Thanks to the answer here by MajkeloDev: https://stackoverflow.com/a/27419974/3998255 for guidance.
This is the final event handler:
public void ShowButton_Click(object sender, EventArgs e)
{
Tile requestingTile = (sender as Button).DataContext as Tile;
if(requestingTile != null)
MessageBox.Show("Viewing item from: " + this.Data);
// Or whatever else you want to do with the object...
}
Also, adding the ItemSource as a XAML attribute:
<ItemsControl x:Name="TileList" ItemsSource="{Binding tiles}">
And setting DataContext in constructor of MainWindow:
public MainWindow()
{
this.DataContext = this;
// Whatever else you want to do...
}
Well it works as required.
I am working on a little project for a contest in my city..and i just hit a brick wall.The thing is: i am creating a userControl in Blend(let's say a canvas,in wich i have a reactangle..a textblock and an image).My problem is that i can not add this to the listboxitem in WPF by code.Addin the userControl one by one in the designer seems to work..but the software is going to work with a variable number of items for the listbox.
private void mainPane1_Loaded(object sender, RoutedEventArgs e)
{
MessageBox.Show("layout updated");
questions cq;
Button btn;
for (int i = 1; i <= 10; i++)
{
btn = new Button();
btn.Content = "intreb" + i.ToString();
cq = new questions();
Canvas.SetZIndex(cq, 17);
//cq.questionHolder.Text = "intrebare" + i.ToString();
listaintrebari.Items.Add(cq);
MessageBox.Show("intrebare" + i.ToString());
listaintrebari.Items.Add(btn);
//MessageBox.Show("layout updated");
}
}
questions is my UserControl and listaintrebari is the listbox.I tried to add some buttons and it works great...but it seems to hate my userControl.
I am waiting for your thoughts on how to resolve this issue, and if you have any sugestions on what other is best to use in my situation and how..it would be great.Thank you!
Ok, here's some actual code that might help you out.
I will be using several WPF concepts that you might want to study further : DataBinding, DataTemplates, ImageSources, ObservableCollections
First you need to create (if you don't have it yet) an underlying class for your Questions. The simplest you can get would be something like this :
internal class Question
{
public ImageSource QuestionImage { get; set; }
public string QuestionText { get; set; }
}
Then in your screen's code behind (yes, we are not at MVVM yet), you should create an ObservableCollection of Question and pouplate them with your questions
I have smth like this:
public ObservableCollection<Question> Questions { get; private set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Questions = new ObservableCollection<Question>();
for (int i = 1; i <= 10; i++)
{
var newQ = new Question { QuestionText = "intrebarea " + i.ToString(), QuestionImage = _get your image as a ImageSource here_ };
Questions.Add(newQ);
}
}
The this.DataContext = this is very important, otherwise your Data Binding will not work.
In your design area, create a list and bind it to the Questions collection you created. The way the question is displayed in the list is driven by the "ItemTemlpate" as below.
<ListBox ItemsSource="{Binding Questions}">
<ListBox.ItemTemplate>
<StackPanel>
<Image Source="{Binding QuestionImage}" Height="20" Width="20"/>
<TextBlock Margin="5" Text="{Binding QuestionText}" />
</StackPanel>
</ListBox.ItemTemplate>
</ListBox>
You can replace the I have there with your UserControl contents or event the UserControl itself, but make sure to preserve the Bindings to the objects in your Question class.
Like I said above, many things might not make sense at this point so make sure you read about them : What is a data Binding, What is a DataContext, What is an ObservableCollection. Also, try looking at MVVM when you get a chance...
Lastly, if you are unsure how to get an ImageSource when you have a jpg or png file in your project:
public ImageSource GetImagesource(string location)
{
var res = new BitmapImage()
res.BeginInit();
res.UriSource = new Uri("pack://application:,,,/ApplicationName;component/" + location);
res.EndInit();
return res;
}
The right way to handle this kind of situation is by having a data model with a collection of your questions. Then bind your ListBox.ItemsSource to the collection and provide a DataTemplate that uses your UserControl.
Use the ListBox's ItemTemplate to define what you want each instance of your object to look like, then bind the ListBox's ItemsSource to a collection of that type.
You need to create a collection (e.g. List) of your control and bind the collection to the ListBox.
I'm learning WPF, and seem to have found something a little odd, which I can't find the reason to anywhere I've searched.
I have a window with one checkbox on it called "chkTest". I have it set to be true by default.
The following code is what I don't understand. Basically I'm trying to set the "chkTest" control to a control I create on the fly. The message box displays the value I set in code, but the control on the window is still set to be true.
Can someone explain the process behind this?
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
CheckBox chk = new CheckBox();
chk.IsChecked = false;
this.chkTest = chk;
MessageBox.Show(chk.IsChecked.Value.ToString());
}
}
Thanks
This is because you're fooling the DependencyProperty system by doing this - remember, getters/setters of DP properties work differently than regular properties. The UI has a trigger on the IsChecked property, but you replace the whole object. Since you didn't actually change IsChecked on the visible checkbox, the trigger doesn't fire and the UI isn't updated.
Here's one way to go about it. First, you give a name to your main Grid - say, LayoutRoot:
<Grid x:Name="LayoutRoot">
<CheckBox x:Name="chkTest" IsChecked="True"></CheckBox>
</Grid>
Then, you say:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
CheckBox chk = new CheckBox();
chk.IsChecked = false;
LayoutRoot.Children.Remove(chkTest);
LayoutRoot.Children.Add(chk);
}
}
And you're done.
I am building a small wpf app in C#. When a button gets clicked a third
party dll function constructs a tree like object. This object is bound
to a treeview. This works fine but takes a bit of time to load. As the
dll function constructs the object it prints progress info to the
console. I want to redirect this into a TextBlock so that the user
gets to see the progress messages.
My window ctor looks like this:
InitializeComponent();
StringRedir s = new StringRedir(ref ProgressTextBlock);
Console.SetOut(s);
Console.SetError(s);
this.DataContext = s;
xaml:
<TextBlock Text="{Binding Path=Text}" Width="244"
x:Name="ProgressTextBlock" TextWrapping="Wrap" />
<TreeView >...</TreeView>
The StringRedir class is shown below. The problem is the TextBlock for
some reason does not get updated with the messages until the TreeView
gets loaded. Stepping through I see the Text property being updated
but the TextBlock is not getting refreshed. I added a MessageBox.Show
() at the point where Text gets updated and this seems to cause the
window to refresh each time and I am able to see each message. So I
guess I need some way to explicitly refresh the screen...but this
doesnt make sense I thought the databinding would cause a visual
refresh when the property changed. What am I missing here? How do I
get it to refresh? Any advice is appreciated!
public class StringRedir : StringWriter , INotifyPropertyChanged
{
private string text;
private TextBlock local;
public string Text {
get{ return text;}
set{
text = text + value;
OnPropertyChanged("Text");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public StringRedir(ref TextBlock t)
{
local = t;
Text = "";
}
public override void WriteLine(string x)
{
Text = x +"\n";
//MessageBox.Show("hello");
}
}
You haven't included the code that is loading the data for the TreeView, but I'm guessing it's being done on the UI thread. If so, this will block any UI updates (including changes to the TextBlock) until it has completed.
So after doing some reading on the WPF threading model ( http://msdn.microsoft.com/en-us/library/ms741870.aspx ) I finally got it to refresh by calling Dispatcher Invoke() with Dispatch priority set to Render. As Kent suggested above UI updates in the dispatcher queue were probably low priority. I ended up doing something like this.
XAML
<TextBox VerticalScrollBarVisibility="Auto"
Text="{Binding Path=Text, NotifyOnTargetUpdated=True}"
x:Name="test" TextWrapping="Wrap" AcceptsReturn="True"
TargetUpdated="test_TargetUpdated"/>
C# target updated handler code
private void test_TargetUpdated(object sender, DataTransferEventArgs e)
{
TextBox t = sender as TextBox;
t.ScrollToEnd();
t.Dispatcher.Invoke(new EmptyDelegate(() => { }), System.Windows.Threading.DispatcherPriority.Render);
}
Note: Earlier I was using a TextBlock but I changed to a TextBox as it comes with scrolling
I still feel uneasy about the whole flow though. Is there a better way to do this?
Thanks to Matt and Kent for their comments. If I had points would mark their answers as helpful.
I believe the problem is in the constructor of your StringRedir class. You're passing in ProgessTextBlock, and you're doing this to it:
local.Text = "";
This is effectively overwriting the previously set value for ProgressTextBlock.Text, which was this:
{Binding Text}
See what I mean? By explicitly setting a value to the TextBlock's Text property, you've cancelled the binding.
If I'm reading right, it looks like the idea of passing a TextBlock into the StringRedir's ctor is a hangover from before you tried binding directly. I'd ditch that and stick with the binding idea as it's more in the "spirit" of WPF.