I am new to both C# and XAML and I am making some sort of reading application.
So I need a TextBlock that word wraps if the title needs more than 1 row to fit. But when it becomes more that 2 rows to fit, wrap a ScrollView on it.
By doing this I could align the rest element tightly whenever it is either 1 or 2(max) row height.
How do I achieve this in XAML or C#?
If you can use a TextBox instead of a TextBlock, it would be easier. A TextBox supports scrolling and has a LineCount property that you can key off of. So for example, I put the a few controls into a StackPanel:
<Grid>
<StackPanel HorizontalAlignment="Left" Height="100" Margin="105,127,0,0" VerticalAlignment="Top" Width="184">
<TextBox Height="23" TextWrapping="Wrap" Text="TextBox" Name="TextBox1"/>
<Button Content="Button" Click="Button_Click_2"/>
</StackPanel>
</Grid>
Then I had some code to update the text. When I hit 2 lines, I grew the TextBox and when I got to three lines, I added scrollbars:
private void Button_Click_2(object sender, RoutedEventArgs e)
{
TextBox1.Text += "More Text";
if (TextBox1.LineCount >= 2)
{
TextBox1.Height = 38;
}
if (TextBox1.LineCount >= 3)
{
TextBox1.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;
}
}
Related
I'm looking for a way to retrieve and force user to enter an int value in a WPF application (developed in C#).
What is the best way to do it ? I've seen "TextBox" can do the Job with value parsing, but i wonder if there is a better way to do it with another WPF control (other than a TextBox) to help user to do so more easily (with up or down button to increment or decrement for example) ?
Goal is to simplify user experience by "guiding" him during "value" selection
Just for you, I've quickly wrote this. This isn't the most efficient way of doing this also you can implement more validations like for decimal numbers or non-negative numbers. Also you can make TexBox IsReadOnly = true to make sure user don't input any non-numeric values.
You can do a lot of the things ! this is just to give you an idea.
XAML
<StackPanel Orientation="Horizontal" Width="200" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBox x:Name="NumberBox" VerticalAlignment="Center" Width="120" Text="0"></TextBox>
<StackPanel Orientation="Vertical" VerticalAlignment="Center"
Margin="2 0 0 0" Spacing="1">
<Button x:Name="UpBtn" Width="50" Height="25" Click="IncrementDecrement">▲</Button>
<Button x:Name="DownBtn" Width="50" Height="25" Click="IncrementDecrement">▼</Button>
</StackPanel>
</StackPanel>
Back-End Code (non MVVM WAY)
private void IncrementDecrement(object sender, RoutedEventArgs e)
{
var senderBtn = sender as Button;
var value = NumberBox.Text.Trim();
if (string.IsNullOrEmpty(value))
{
value = 0.ToString();
}
bool success = Int32.TryParse(value, out var number);
if (!success)
{
// show error
return;
}
NumberBox.Text = senderBtn.Name == "UpBtn" ? (++number).ToString() : (--number).ToString();
}
Result
Here is my scenario. My table has fixed number of columns, say 2, and initially, it has only one visible row, but when the focus is on the last column of row 1 and the user press 'tab', row 2 will be made visible.
My problem is that I can't dynamically select the row I want to make visible because I have to specify its x:Name during compilation.
Below is my current work.
.xaml file
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" x:Name="SP1">
<TextBox Text="1-1"/>
<TextBox Text="1-2" KeyDown="showNextLine"/>
</StackPanel>
<StackPanel Orientation="Horizontal" x:Name="SP2" Visibility="Collapsed">
<TextBox Text="2-1"/>
<TextBox Text="2-2"/>
</StackPanel>
<!--the remaining rows...-->
</StackPanel>
.cs file
private int lastRowIndex = 1;
private void showNextLine(object sender, KeyRoutedEventArgs e)
{
lastRowIndex++;
string nextLineName = "SP" + lastRowIndex.ToString();
nextLineName.Visibility = Visibility.Visible; // which causes error because nextLineName is string instead of StackPanel
}
Besides, my current implementation is to create 50 rows and make the last 49 invisible initially, and I am open to any method to group all the TextBox more systematically or flexibly.
Thanks for reading.
You could give the parent StackPanel an x:Name or keep a reference to it if you create it dynamically:
<StackPanel x:Name="root" Orientation="Vertical">
<StackPanel Orientation="Horizontal" x:Name="SP1">
<TextBox Text="1-1"/>
<TextBox Text="1-2" KeyDown="showNextLine"/>
</StackPanel>
<StackPanel Orientation="Horizontal" x:Name="SP2" Visibility="Collapsed">
<TextBox Text="2-1"/>
<TextBox Text="2-2"/>
</StackPanel>
<!--the remaining rows...-->
</StackPanel>
...and then get a reference to a child StackPanel using the Children property and some basic LINQ:
private void showNextLine(object sender, KeyRoutedEventArgs e)
{
lastRowIndex++;
string nextLineName = "SP" + lastRowIndex.ToString();
StackPanel child = root.Children.OfType<StackPanel>().FirstOrDefault(x => x.Name == nextLineName);
if (child != null)
child.Visibility = Visibility.Visible;
}
How could I create a StackPanel dynamically?
Like this:
var sp = new StackPanel { Name = "SP3", Orientation = Orientation.Horizontal, Visibility = Visibility.Collapsed };
sp.Children.Add(new TextBlock { Text = "3-1" });
var txt = new TextBlock() { Text = "3-2" };
txt.KeyDown += showNextLine;
sp.Children.Add(txt);
root.Children.Add(sp);
I can't think of an easy way to do the first part of this (since you have 50 stack panels), but if you put all of them in a dictionary, then you could update them using just the key.
Here's the dictionary part done manually:
Dictionary<int, StackPanel> myStackPanels = new Dictionary<int, StackPanel>();
myStackPanels.Add(1, SP1);
myStackPanels.Add(2, SP2);
Then, here's what ShowNextLine would look like:
private void showNextLine(object sender, KeyRoutedEventArgs e)
{
lastRowIndex++;
// Modify the StackPanel whose key is lastRowIndex;
myStackPanels[lastRowIndex] = Visibility.Visible;
}
I am dynamically adding textboxes based on a button click inside the stackpanel.But the textboxes are not visible in the UI .
Here is the code used for creating textboxs inside stackpanel.
public void GenerateControls()
{
TextBox txtNumber = new TextBox();
txtNumber.Name = "txtNumber";
txtNumber.Text = "1776";
txtNumber.Background= Brushes.Red;
panel1.Children.Add(txtNumber);
}
why its not visible..??and here is the XAML part of stackpanel
<StackPanel Name="panel1" Grid.Column="1" HorizontalAlignment="Left" Height="151" Margin="427,60,0,0" Grid.Row="2" VerticalAlignment="Top" Width="216">
<StackPanel Height="144">
</StackPanel>
</StackPanel>
If you are going to be adding controls dynamically, do not restrict the height (or even width) of the container you are adding to.
Update your XAML to have auto height/width.
<StackPanel Name="panel1"
Grid.Column="1"
Height="Auto"
Width="Auto"
Margin="427,60,0,0"
Grid.Row="2"
VerticalAlignment="Top"
HorizontalAlignment="Left" >
<StackPanel Height="144">
</StackPanel>
</StackPanel>
Also, once you add a new child, make sure you are updating the StackPanel layout.
public void GenerateControls()
{
TextBox txtNumber = new TextBox();
txtNumber.Name = "txtNumber";
txtNumber.Text = "1776";
txtNumber.Background= Brushes.Red;
panel1.Children.Add(txtNumber);
panel1.UpdateLayout();
}
In your xaml code, there is a stackpanel in your 'panel', it will be the 1st child of 'panel'.
And its height is 144px. your 'panel1' is 151 px.
So when you add textboxes into 'panel', they will be displayed behind the 144px stackpanel.
There is only 7px to display them. So they will not display on your window.
I'm using a list of textboxes for a registering document in a WP8 app.
The number of textboxes is quite large, so the user has to scroll between them.
To navigate between one field to another, I added two applicationbarIcons, next and previous. Pressing on next will change the focus to the next textbox from list, and scroll the content of the scroll viewer with the height of the textbox (in this case 50).
However, sometimes, when switching the focus to the element bellow, the keyboard covers the text box. (the content doesn't scroll up).
Is there a way to force the textbox to move above the keyboard, even if it is in a scroll view?
<ScrollViewer x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel>
<TextBlock Text="{Binding Source={StaticResource LocalizedStrings}, Path=LocalizedResources.STRING_CONTACT}" Margin="10,5" FontWeight="SemiBold" Foreground="#878780"></TextBlock>
<StackPanel Margin="10,5" Height="190" Background="#F4F3F4">
<TextBox LostFocus="firstNameTxt_LostFocus_1" GotFocus="firstNameTxt_GotFocus_1" Margin="0,-7" FontSize="23" x:Name="firstNameTxt" BorderThickness="0" Background="Transparent" InputScope="PersonalFullName"><TextBox>
<TextBox LostFocus="firstNameTxt_LostFocus_1" GotFocus="firstNameTxt_GotFocus_1" Margin="0,-7" FontSize="23" x:Name="lastNameTxt" BorderThickness="0" Background="Transparent" InputScope="PersonalFullName"></my:DefaultTextBox>
<TextBox LostFocus="firstNameTxt_LostFocus_1" GotFocus="firstNameTxt_GotFocus_1" Margin="0,-7" FontSize="23" x:Name="MobileTxt" BorderThickness="0" InputScope="Number" Background="Transparent" ></TextBox>
<TextBox LostFocus="firstNameTxt_LostFocus_1" GotFocus="firstNameTxt_GotFocus_1" Margin="0,-7" FontSize="23" x:Name="EmailTxt" BorderThickness="0" Background="Transparent">
</StackPanel>
</ScrollViewer>
Code behind:
void left_Click(object sender, EventArgs e)
{
int index = this.controls.IndexOf(currentControl) - 1;
if (index == -1)
{
this.Focus();
return;
}
currentControl = this.controls[index];
ContentPanel.ScrollToVerticalOffset(ContentPanel.VerticalOffset - 50);
currentControl.Focus();
}
This is a common issue on WP8. When a textbox is focused, it will translate Application 's RootVisual to bring it into view. This doesn't work well in some cases (when clipboard is on, or in your case). A workaround is manually translating RootVisual to a desired vertical offset on GotFocus and LostFocus events of TextBox.
private void TranslateRootVisualY(int yNew)
{
var rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;
rootFrame.RenderTransform = new CompositeTransform() {TranslateY = yNew};
}
In your case, you can eliminate the automatic translation and make ScrollViewer scroll to desired offset in GotFocus event:
private void firstNameTxt_GotFocus_1(object sender, RoutedEventArgs e)
{
TranslateRootVisualY(0);
Dispatcher.BeginInvoke(() =>{
double destOffset;
//...calculate destination offset
ContentPanel.ScrollToVerticalOffset(destOffset);
});
}
destOffset can be calculated from sender and other function like GetRectFromCharacterIndex
Basically, I'm trying to make the canvas listen for a touch input (tap) and will increment the number of taps on screen. It isn't working when I touch the screen on my device. I debugged my code and nothing seems out of the ordinary except that the touch is not detected. I checked ZIndex and the canvas is in front of the screen to be touchable. How do I make it work?
XAML:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Name="counter" FontSize="150" HorizontalAlignment="Center" TextWrapping="Wrap" Text="0" VerticalAlignment="Center" Margin="188,10,187,397"/>
<Button Content="Reset" HorizontalAlignment="Stretch" Margin="-18,535,-18,0" VerticalAlignment="Top" Click="Button_Click"/>
<Canvas ZIndex="0" Name="Canvas" HorizontalAlignment="Center" Height="535" VerticalAlignment="Top" Width="446" MouseLeftButtonDown="Canvas_MouseLeftButtonDown" MouseLeftButtonUp="Canvas_MouseLeftButtonUp" MouseLeave="Canvas_MouseLeave"/>
</Grid>
C#:
int taps = 0; // create var to detect number of times, user touches the screen
// Constructor
public MainPage()
{
InitializeComponent();
}
// method to register the touch as the finger is placed on the screen
private void Canvas_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
//Canvas c = sender as Canvas;
counter.Text = "TOUCHED!";
}
//method register the touch as the finger is lifting up from the screen
private void Canvas_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
//Canvas c = sender as Canvas;
taps++;
counter.Text = taps.ToString(); //convert var from int to string
}
//method register the touch as the finger leaves the area of the screen
private void Canvas_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
//Canvas c = sender as Canvas;
MessageBox.Show("You left the screen without lifting your finger. That does not count as a tap!", "Caution!", MessageBoxButton.OK);
}
// method to reset the counter to zero when button is pressed and released
private void Button_Click(object sender, RoutedEventArgs e)
{
taps = 0; // reset the count
counter.Text = taps.ToString(); // convert var from int to string
}
I don't know why you want to do it with Canvas - it won't work as you have nothing in this Canvas, so it can't register your click/tap, Canvas is also hard to adjust to screen. I think it can be done simpler way if you want to do it with MouseUp/Down - subscribe directly to Grid containing your elements instead of filling this Grid with additional Canvas:
In XAML:
<Grid x:Name="ContentPanel" Margin="12,0,12,0" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="7*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<TextBlock Name="counter" FontSize="150" HorizontalAlignment="Center" TextWrapping="Wrap" Text="0" VerticalAlignment="Center" Grid.Row="0"/>
<Button Content="Reset" HorizontalAlignment="Stretch" VerticalAlignment="Center" Click="Button_Click" Grid.Row="1"/>
<TextBlock Name="Touched" FontSize="50" HorizontalAlignment="Center" TextWrapping="Wrap" Text="Touched" VerticalAlignment="Center" Visibility="Collapsed" Grid.Row="2"/>
</Grid>
In code behind:
private int taps = 0;
public MainPage()
{
InitializeComponent();
ContentPanel.MouseLeftButtonDown += ContentPanel_MouseLeftButtonDown;
ContentPanel.MouseLeftButtonUp += ContentPanel_MouseLeftButtonUp;
}
private void ContentPanel_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
taps++;
counter.Text = taps.ToString(); //convert var from int to string
Touched.Visibility = Visibility.Collapsed;
}
private void ContentPanel_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
Touched.Visibility = Visibility.Visible;
}
// method to reset the counter to zero when button is pressed and released
private void Button_Click(object sender, RoutedEventArgs e)
{
taps = 0; // reset the count
counter.Text = taps.ToString(); // convert var from int to string
}
As you can see I've subscribed to Grid events (which covers whole screen) - but to make it work I had to set its Background Brush to Transparent, otherwise it will work only if you touch text.
There are many other ways to make your App work, but I hope this will help.
Is there a reason why you don't use the touch-events?
Instead of using MouseLeftButtonDown and MouseLeftButtonUp you should use TouchDown and TouchUp.
Only when you don't handle the touch events or the manipulation events they will be mapped to mouse events. In my experience with touch a single tap also not always gets mapped to MouseLeftButtonDown. As far as I know you could also with mouse events only recoginse one finger. When you want to count more fingers it's necessary to use the TouchDown/TouchUp events.
The problem lies in the overlapping style of the grid
so either make grid rows or define a stackpanel inside the grid, something like this.
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<stackpanel>
<TextBlock Name="counter" FontSize="150" HorizontalAlignment="Center" TextWrapping="Wrap" Text="0" Margin="0,0,0,0"/>
<Canvas ZIndex="0" Name="Canvas" HorizontalAlignment="Center" Height="535" VerticalAlignment="Top" Width="446" MouseLeftButtonDown="Canvas_MouseLeftButtonDown" MouseLeftButtonUp="Canvas_MouseLeftButtonUp" MouseLeave="Canvas_MouseLeave"/>
<Button Content="Reset" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Top" Click="Button_Click"/>
</stackpanel>
</Grid>
Try and check now.
You should set your Background property. If you don't want any background set it to Transparent:
<Canvas ZIndex="99" Background="Transparent" Name="Canvas" HorizontalAlignment="Center" Height="535" VerticalAlignment="Top" Width="446" Tapped="Canvas_CountMyTaps"/>
(If you want the canvas to be on Top be sure to make it have a greater ZIndex than the other elements that it overlaps)
If not set (the default value is null) the element won't capture any taps/click etc, it will be as if they "fall through".
Also, consider using the Tapped event which is a "higher level" event that will respond to clicks, taps with the finger, stylus, etc.