I'm trying to create a simple Word add-in.
I the Ribbon a have create a button and I want to change its label depending on the type of word document.
the document is a mail merge that has two types of models: Demand and Response.
I want to have button label: CREATE DEMAND for Demmand and CREATE RESPONSE for Response.
I tried something like this, but it does not work:
private void Ribbon1_Load(object sender, RibbonUIEventArgs e)
{
Microsoft.Office.Interop.Word.Document doc = Globals.ThisAddIn.Application.ActiveDocument;
//Word.MailMerge mailMerge = doc.MailMerge;
if (doc.MailMerge.DataSource.FieldNames.Count!=0)
{
foreach (Microsoft.Office.Interop.Word.MailMergeFieldName f in doc.MailMerge.DataSource.FieldNames)
{
if (f.Name.Equals("Response"))
{
this.button1.Label = "Create a response";
break;
}
else if (f.Name.Equals("Demand"))
{
this.button1.Label = "Create a demand";
break;
}
}
}
}
the value of doc.MailMerge.DataSource.FieldNames.Count is always equal to 0
Do you have any idea how i can do it?
The visual designer for ribbons in word is a little limited, you can get much more functionality by using an XML ribbon, although it is a little more work.
You could do handle this by creating two buttons and then creating a GetVisible method in your ribbon code. The XML would be the following:
<button id="ButtonCreateDemand" label="Create Demand" size="normal"getVisible="GetVisible" onAction="Call_CreateDemand"/>
<button id="ButtonCreateResponse" label="Create Response" size="normal"getVisible="GetVisible" onAction="Call_CreateResponse"/>
The code would be something like this
public bool GetVisible(Office.IRibbonControl control)
{
//Check for demand\response
switch(control.Id.ToLower())
{
case "buttoncreatedemand":
return GetDemand();
case "buttoncreateresponse":
return !GetDemand();
}
}
private bool GetDemand()
{
Microsoft.Office.Interop.Word.Document doc = Globals.ThisAddIn.Application.ActiveDocument;
//Word.MailMerge mailMerge = doc.MailMerge;
if (doc.MailMerge.DataSource.FieldNames.Count!=0)
{
foreach (Microsoft.Office.Interop.Word.MailMergeFieldName f in doc.MailMerge.DataSource.FieldNames)
{
if (f.Name.Equals("Response"))
{
return false;
}
}
}
return true;
}
Alternatively you could use a GetLabel() method to return the label for the button.
You add the DocumentChange() event to the ThisAddIn class
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Globals.ThisAddIn.Application.DocumentChange += new Word.ApplicationEvents4_DocumentChangeEventHandler(DocumentChange);
}
Related
I am developing a new external command for Revit. it needs a progress bar + a button to cancel its execution in any moment.
In order to get it, I haver implemented a external event.
Implementing an external event handler with de code to be executed by the command.
public class GestorDeEventoExterno : IExternalEventHandler
{
public bool CancellationRequested { get; set; }
private VentanaDeProgreso progressAndcancelWindow;
private EventWaitHandle eventWait;
public void Execute(UIApplication aplicacionDeLaIU)
{
using (this.eventWait = new AutoResetEvent(false))
{
// New thread for the progress bar.
Thread progressBarThread = new Thread(new ThreadStart(() =>
{
// Populating the progress bar window.
this.progressAndcancelWindow = new VentanaDeProgreso(this);
progressAndcancelWindow.Show();
// Chenge the state of the wait event.
this.eventWait.Set();
Dispatcher.Run();
}));
progressBarThread.SetApartmentState(ApartmentState.STA);
progressBarThread.IsBackground = true;
progressBarThread.Start();
this.eventWait.WaitOne();
}
// Get the current revit document.
Document documentoActivo = aplicacionDeLaIU.ActiveUIDocument.Document;
// Code to simulate the revit command operation.
for (int i = 0;
i <= 100;
i++)
{
// Code to be executed if a cancellation has been requested.
if (this.CancellationRequested)
{
TaskDialog.Show("Test", "Cancel");
this.progressAndcancelWindow.Dispatcher.Invoke(new Action(this.progressAndcancelWindow.Close));
return;
}
this.progressAndcancelWindow.ActualizarProgreso($"loop number: {i}", i, 100);
Thread.Sleep(100);
}
this.progressAndcancelWindow.Dispatcher.Invoke(new Action(this.progressAndcancelWindow.Close));
TaskDialog.Show("Test", "END");
}
public string GetName()
{
return "test";
}
}
Implementing an external command to register the external event and populate the main window
public class Class1 : IExternalCommand
{
public Result Execute(
ExternalCommandData externalCommandData,
ref string message,
ElementSet elements)
{
// Registering the external event.
GestorDeEventoExterno externalEventHandler = new GestorDeEventoExterno();
ExternalEvent externalEvent = ExternalEvent.Create(externalEventHandler);
// Populating the main window.
VentanaPrincipal mainWindow = new VentanaPrincipal(
externalEvent);
mainWindow.Show();
return Result.Succeeded;
}
}
Finally, the code behind of the progress bar window
public partial class VentanaDeProgreso : Window
{
private GestorDeEventoExterno externalEventHandler;
public void ActualizarProgreso(
string texto,
int valorActual,
int valortotal = 100)
{
this.Dispatcher.Invoke(
new Action<string, int, int>(
delegate (string txt, int vActual, int vTotal)
{
this.IndicadorDeProgreso.Value = valorActual;
this.IndicadorDeProgreso.Maximum = vTotal;
this.Texto.Text = txt;
}),
System.Windows.Threading.DispatcherPriority.Background,
texto,
valorActual,
valortotal);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// AsignaciĆ³n de valor verdadero a la propiedad de cancelaciĆ³n solicitada del evento externo.
this.externalEventHandler.CancellationRequested = true;
}
public VentanaDeProgreso(GestorDeEventoExterno externalEventHandler)
{
InitializeComponent();
this.externalEventHandler= externalEventHandler;
}
}
AS you can see, the progress window has the external event handler as a property and the cancel button click event sets the property 'CancellationRequested'.
My question is: How can I improve it?
You do not need an external event to cancel your command.
You only need an external event to cancel submit a request to execute Revit API functionality from some context in which it is not available.
Your cancellation requires no Revit API functionality, just your own stuff, hence no external event.
Therefore, you can restructure the whole solution much more simply. Kiss!
Goal
I'd like to have my ComboBox items suggest and append its items when something is contained in them, not just via the StartsWith function.
My ComboBox is bound to a DataView which contains clients [CompanyName], [Address], [City] in a long concatenation.
I want my users to be able to type in the city and still find the records which matches with all of the fields above. I know this is possible with Infragistics but I don't have that package.
Search Term: "Sher"
Costco, 123 1st Avenue, Sherbrooke
Provigo, 344 Ball Street, Sherbrooke
Sherbox, 93 7th Street, Montreal
Is this possible in VB.Net or should I be searching for something else?
I did some research and found the following question:
Override Winforms ComboBox Autocomplete Suggest Rule
In that question they reffer to another question:
C# AutoComplete
Let's quote the best answer from that question
The existing AutoComplete functionality only supports searching by
prefix. There doesn't seem to be any decent way to override the
behavior.
Some people have implemented their own autocomplete functions by
overriding the OnTextChanged event. That's probably your best bet.
For example, you can add a ListBox just below the TextBox and set
its default visibility to false. Then you can use the OnTextChanged
event of the TextBox and the SelectedIndexChanged event of the
ListBox to display and select items.
This seems to work pretty well as a rudimentary example:
public Form1()
{
InitializeComponent();
acsc = new AutoCompleteStringCollection();
textBox1.AutoCompleteCustomSource = acsc;
textBox1.AutoCompleteMode = AutoCompleteMode.None;
textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
}
private void button1_Click(object sender, EventArgs e)
{
acsc.Add("[001] some kind of item");
acsc.Add("[002] some other item");
acsc.Add("[003] an orange");
acsc.Add("[004] i like pickles");
}
void textBox1_TextChanged(object sender, System.EventArgs e)
{
listBox1.Items.Clear();
if (textBox1.Text.Length == 0)
{
hideResults();
return;
}
foreach (String s in textBox1.AutoCompleteCustomSource)
{
if (s.Contains(textBox1.Text))
{
Console.WriteLine("Found text in: " + s);
listBox1.Items.Add(s);
listBox1.Visible = true;
}
}
}
void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
{
textBox1.Text = listBox1.Items[listBox1.SelectedIndex].ToString();
hideResults();
}
void listBox1_LostFocus(object sender, System.EventArgs e)
{
hideResults();
}
void hideResults()
{
listBox1.Visible = false;
}
There's a lot more you could do without too much effort: append text
to the text box, capture additional keyboard commands, and so forth.
Improved the technique demonstrated by BenD in his answer so as to have the mechanism handle a bit more elegantly certain cornercases:
public sealed class CCComboboxAutocomplete : ComboBox
{
public CCComboboxAutocomplete()
{
AutoCompleteMode = AutoCompleteMode.Suggest; //crucial otherwise exceptions occur when the user types in text which is not found in the autocompletion list
}
protected override void OnTextChanged(EventArgs e)
{
try
{
if (DesignMode || !string.IsNullOrEmpty(Text) || !Visible) return;
ResetCompletionList();
}
finally
{
base.OnTextChanged(e);
}
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
try
{
if (DesignMode) return;
if (e.KeyChar == '\r' || e.KeyChar == '\n')
{
e.Handled = true;
if (SelectedIndex == -1 && Items.Count > 0 && Items[0].ToString().ToLowerInvariant().StartsWith(Text.ToLowerInvariant()))
{
Text = Items[0].ToString();
}
DroppedDown = false;
return; //0
}
BeginInvoke(new Action(ReevaluateCompletionList)); //1
}
finally
{
base.OnKeyPress(e);
}
}
//0 Guardclose when detecting any enter keypresses to avoid a glitch which was selecting an item by means of down arrow key followed by enter to wipe out the text within
//1 Its crucial that we use begininvoke because we need the changes to sink into the textfield Omitting begininvoke would cause the searchterm to lag behind by one character That is the last character that got typed in
private void ResetCompletionList()
{
_previousSearchterm = null;
try
{
SuspendLayout();
var originalList = (object[])Tag;
if (originalList == null)
{
Tag = originalList = Items.Cast<object>().ToArray();
}
if (Items.Count == originalList.Length) return;
while (Items.Count > 0)
{
Items.RemoveAt(0);
}
Items.AddRange(originalList);
}
finally
{
ResumeLayout(performLayout: true);
}
}
private string _previousSearchterm;
private void ReevaluateCompletionList()
{
var currentSearchterm = Text.ToLowerInvariant();
if (currentSearchterm == _previousSearchterm) return; //optimization
_previousSearchterm = currentSearchterm;
try
{
SuspendLayout();
var originalList = (object[])Tag;
if (originalList == null)
{
Tag = originalList = Items.Cast<object>().ToArray(); //0
}
var newList = (object[])null;
if (string.IsNullOrEmpty(currentSearchterm))
{
if (Items.Count == originalList.Length) return;
newList = originalList;
}
else
{
newList = originalList.Where(x => x.ToString().ToLowerInvariant().Contains(currentSearchterm)).ToArray();
}
try
{
while (Items.Count > 0) //1
{
Items.RemoveAt(0);
}
}
catch
{
try
{
Items.Clear();
}
catch
{
}
}
Items.AddRange(newList.ToArray()); //2
}
finally
{
if (currentSearchterm.Length >= 2 && !DroppedDown)
{
DroppedDown = true; //3
Cursor.Current = Cursors.Default; //4
Text = currentSearchterm; //5
Select(currentSearchterm.Length, 0);
}
ResumeLayout(performLayout: true);
}
}
//0 backup original list
//1 clear list by loop through it otherwise the cursor would move to the beginning of the textbox
//2 reset list
//3 if the current searchterm is empty we leave the dropdown list to whatever state it already had
//4 workaround for the fact the cursor disappears due to droppeddown=true This is a known bu.g plaguing combobox which microsoft denies to fix for years now
//5 Another workaround for a glitch which causes all text to be selected when there is a matching entry which starts with the exact text being typed in
}
Sorry for another answer in C# but I have a more improved answer based on xDisruptor's code.
Using kinda behavior (decorator).
You don't have to subclass ComboBox and change all existing combos in the designed.
Be careful when using Datasource instead of Items collection, because it'll raise an exception.
Code:
public class AutoCompleteBehavior
{
private readonly ComboBox comboBox;
private string previousSearchterm;
private object[] originalList;
public AutoCompleteBehavior(ComboBox comboBox)
{
this.comboBox = comboBox;
this.comboBox.AutoCompleteMode = AutoCompleteMode.Suggest; // crucial otherwise exceptions occur when the user types in text which is not found in the autocompletion list
this.comboBox.TextChanged += this.OnTextChanged;
this.comboBox.KeyPress += this.OnKeyPress;
this.comboBox.SelectionChangeCommitted += this.OnSelectionChangeCommitted;
}
private void OnSelectionChangeCommitted(object sender, EventArgs e)
{
if (this.comboBox.SelectedItem == null)
{
return;
}
var sel = this.comboBox.SelectedItem;
this.ResetCompletionList();
this.comboBox.SelectedItem = sel;
}
private void OnTextChanged(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(this.comboBox.Text) || !this.comboBox.Visible || !this.comboBox.Enabled)
{
return;
}
this.ResetCompletionList();
}
private void OnKeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r' || e.KeyChar == '\n')
{
e.Handled = true;
if (this.comboBox.SelectedIndex == -1 && this.comboBox.Items.Count > 0
&& this.comboBox.Items[0].ToString().ToLowerInvariant().StartsWith(this.comboBox.Text.ToLowerInvariant()))
{
this.comboBox.Text = this.comboBox.Items[0].ToString();
}
this.comboBox.DroppedDown = false;
// Guardclause when detecting any enter keypresses to avoid a glitch which was selecting an item by means of down arrow key followed by enter to wipe out the text within
return;
}
// Its crucial that we use begininvoke because we need the changes to sink into the textfield Omitting begininvoke would cause the searchterm to lag behind by one character That is the last character that got typed in
this.comboBox.BeginInvoke(new Action(this.ReevaluateCompletionList));
}
private void ResetCompletionList()
{
this.previousSearchterm = null;
try
{
this.comboBox.SuspendLayout();
if (this.originalList == null)
{
this.originalList = this.comboBox.Items.Cast<object>().ToArray();
}
if (this.comboBox.Items.Count == this.originalList.Length)
{
return;
}
while (this.comboBox.Items.Count > 0)
{
this.comboBox.Items.RemoveAt(0);
}
this.comboBox.Items.AddRange(this.originalList);
}
finally
{
this.comboBox.ResumeLayout(true);
}
}
private void ReevaluateCompletionList()
{
var currentSearchterm = this.comboBox.Text.ToLowerInvariant();
if (currentSearchterm == this.previousSearchterm)
{
return;
}
this.previousSearchterm = currentSearchterm;
try
{
this.comboBox.SuspendLayout();
if (this.originalList == null)
{
this.originalList = this.comboBox.Items.Cast<object>().ToArray(); // backup original list
}
object[] newList;
if (string.IsNullOrEmpty(currentSearchterm))
{
if (this.comboBox.Items.Count == this.originalList.Length)
{
return;
}
newList = this.originalList;
}
else
{
newList = this.originalList.Where(x => x.ToString().ToLowerInvariant().Contains(currentSearchterm)).ToArray();
}
try
{
// clear list by loop through it otherwise the cursor would move to the beginning of the textbox
while (this.comboBox.Items.Count > 0)
{
this.comboBox.Items.RemoveAt(0);
}
}
catch
{
try
{
this.comboBox.Items.Clear();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
this.comboBox.Items.AddRange(newList.ToArray()); // reset list
}
finally
{
if (currentSearchterm.Length >= 1 && !this.comboBox.DroppedDown)
{
this.comboBox.DroppedDown = true; // if the current searchterm is empty we leave the dropdown list to whatever state it already had
Cursor.Current = Cursors.Default; // workaround for the fact the cursor disappears due to droppeddown=true This is a known bu.g plaguing combobox which microsoft denies to fix for years now
this.comboBox.Text = currentSearchterm; // Another workaround for a glitch which causes all text to be selected when there is a matching entry which starts with the exact text being typed in
this.comboBox.Select(currentSearchterm.Length, 0);
}
this.comboBox.ResumeLayout(true);
}
}
}
Usege:
new AutoCompleteBehavior(this.comboBoxItems);
this.comboBoxItems.Items.AddRange(new object[] { "John", "Tina", "Doctor", "Alaska" });
TIP: Can be further improved by making an extension to the ComboBox class like myCombo.ToAutoComplete()
A ComboBox,TextBox and I think a DropDownList has AutoComplete properties
Look at http://msdn.microsoft.com/en-us/library/system.windows.forms.combobox.autocompletemode(v=vs.110).aspx
It explains which AutoCompleteMode you should use and how to set the AutoCompleteSource
You could try the following lines, it worked for me
cbxName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
cbxName.AutoCompleteSource = AutoCompleteSource.ListItems;
Goal
I want to display the text that I put in the Label's "Error on ErrorProvider1" attribute whenever I get an error. See the following label's attributes below.
I try to display the text in the red rectangle into my ErrorProvider1 SetError(control, value) function.
If TextBox1.Text.Trim.Contains("'") Then
ErrorProvider1.SetError(lblErr, ErrorProvider1.GetError(lblErr))
Else
ErrorProvider1.SetError(lblErr, "")
End If
How can I retrieve the 'Error on ErrorProvider1' text from the lblErr to display it in the ErrorProvider1 SetError value?
The ErrorProvider component is very awkward to use effectively. It is fixable however, I'll give an example in C# that extends the component with some new capabilities:
ShowError(Control ctl, bool enable) displays the text that you entered at design-time when the enable argument is true. The easier-to-use version of SetError().
HasErrors returns true if the any active warning icons are displayed. Handy in your OK button's Click event handler.
FocusError() sets the focus to the first control that has a warning icon, if any. It returns false if no warnings are remaining.
SetError() is a replacement of ErrorProvider.SetError(). You only need it if you add any controls after the form's Load event fired or if you need to modify the warning text.
Add a new class to your project and paste the code shown below. Compile. Drop it from the top of the toolbox onto the form. The design-time behavior is identical. Modestly tested.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.ComponentModel.Design;
class MyErrorProvider : ErrorProvider {
public void ShowError(Control ctl, bool enable) {
// Easy to use version of SetError(), uses design-time text
if (!enable) base.SetError(ctl, "");
else {
if (errors.ContainsKey(ctl)) base.SetError(ctl, errors[ctl]);
else base.SetError(ctl, "No error text available");
}
}
public bool HasErrors {
// True if any errors are present
get {
foreach (var err in errors)
if (!string.IsNullOrEmpty(base.GetError(err.Key))) return true;
return false;
}
}
public bool FocusError() {
// Set the focus to the first control with an active error
foreach (var err in errors) {
if (!string.IsNullOrEmpty(base.GetError(err.Key))) {
err.Key.Focus();
return true;
}
}
return false;
}
public new void SetError(Control ctl, string text) {
// Use this only to add/modify error text after the form's Load event
if (!string.IsNullOrEmpty(text)) {
if (errors.ContainsKey(ctl)) errors[ctl] = text;
else errors.Add(ctl, text);
}
base.SetError(ctl, text);
}
private void initialize(object sender, EventArgs e) {
// Preserve error text
copyErrors(((Form)sender).Controls);
}
private void copyErrors(Control.ControlCollection ctls) {
foreach (Control ctl in ctls) {
var text = this.GetError(ctl);
if (!string.IsNullOrEmpty(text)) {
errors.Add(ctl, text);
base.SetError(ctl, "");
}
copyErrors(ctl.Controls);
}
}
private Dictionary<Control, string> errors = new Dictionary<Control, string>();
// Plumbing to hook the form's Load event
[Browsable(false)]
public new ContainerControl ContainerControl {
get { return base.ContainerControl; }
set {
if (base.ContainerControl == null) {
var form = value.FindForm();
if (form != null) form.Load += initialize;
}
base.ContainerControl = value;
}
}
public override ISite Site {
set {
// Runs at design time, ensures designer initializes ContainerControl
base.Site = value;
if (value == null) return;
IDesignerHost service = value.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (service == null) return;
IComponent rootComponent = service.RootComponent;
this.ContainerControl = rootComponent as ContainerControl;
}
}
}
Your issue is that you are replacing the error message when nothing is wrong. As noted in your comment below, you are storing the localized error message in the label's Tag, so you can do the following:
If TextBox1.Text.Trim.Contains("'") Then
ErrorProvider1.SetError(lblErr, lblErr.Tag)
Else
ErrorProvider1.SetError(lblErr, "")
End If
You were correct to use ErrorProvider1.GetError(Control) to get the value. It's just that you're more than likely replacing it with an empty string before you were retrieving it.
I think i'm missing something obvious...
I'm using the Telerik Rad controls for WPF but i assume that the Rich text box uses some similar implementation for the mail merge functionality.
I want to have some friendly names on my mail merge fields. (namely spaces in the field names)
So i have a class for instance
Public Class someclass
{
<DisplayName("This is the complex description of the field")>
Public property thisfieldnamehasacomplexdescription as string
Public property anothercomplexfield as string
}
This is the only way i know to get "Friendly" names in the dropdown that is the mail merge.
So the two fields turn up okay as :
"This is the complex description of the field"
"anothercomplexfield"
but only anothercomplexfield actually populates with data when you do the merge.
Am i going to have to template the raddropdownbutton that holds the mail merge fields?
Is there an example of this somewhere?
Also a sub question. How do i add a scroll bar on these things?
(also i know this board is not a TELERIK specific board (duh!) but this might be useful to someone in the future. So i'll copy the answer i get from Telerik into here!
http://www.telerik.com/community/forums/wpf/richtextbox/558428-radrichtextbox-mailmerge---using-displayname-to-create-a-friendly-name-with-spaces.aspx )
This is what telerik gave me:
With the default MergeFields, it is not possible to change the display name fragment of the field in order to achieve a more friendly look. This should be possible if you implement a custom MergeField by deriving from the MergeField class. Here is a sample implementation that shows how this can be done:
public class CustomMergeField : MergeField
{
private const string CustomFieldName = "CustomField";
static CustomMergeField()
{
CodeBasedFieldFactory.RegisterFieldType(CustomMergeField.CustomFieldName, () => { return new CustomMergeField(); });
}
public override string FieldTypeName
{
get
{
return CustomMergeField.CustomFieldName;
}
}
public override Field CreateInstance()
{
return new CustomMergeField();
}
protected override DocumentFragment GetDisplayNameFragment()
{
return base.CreateFragmentFromText(string.Format(Field.DisplayNameFragmentFormat, this.GetFriendlyFieldName(this.PropertyPath)));
}
private string GetFriendlyFieldName(string fieldName)
{
int lettersInEnglishAlphabet = 26;
List<char> separators = new List<char>(lettersInEnglishAlphabet);
for (int i = 0; i < lettersInEnglishAlphabet; i++)
{
separators.Add((char)('A' + i));
}
StringBuilder newFieldName = new StringBuilder();
int previousIndex = 0;
for (int i = 1; i < fieldName.Length; i++)
{
if (separators.Contains(fieldName[i]))
{
if (previousIndex > 0)
{
newFieldName.Append(" ");
}
newFieldName.Append(fieldName.Substring(previousIndex, i - previousIndex));
previousIndex = i;
}
}
newFieldName.Append(" " + fieldName.Substring(previousIndex));
return newFieldName.ToString();
}
}
Note that the fragment that is shown when the DisplayMode is Code cannot be changed.
As for your other question, you can change the content of the dropdown button to show the friendly name of the fields and to include a scrollbar in the following way:
1. First, remove the binding of the button to the InsertMergeFieldEmptyCommand from XAML and give it a name (e.g. insertMergeField).
2. Next, add the following code in code-behind:
AddMergeFieldsInDropDownContent(this.insertMergeFieldButton);
private void AddMergeFieldsInDropDownContent(RadRibbonDropDownButton radRibbonDropDownButton)
{
Grid grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(100, GridUnitType.Pixel) });
ScrollViewer scrollViewer = new ScrollViewer();
scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
StackPanel stackPanel = new StackPanel();
foreach (string fieldName in this.editor.Document.MailMergeDataSource.GetColumnNames())
{
RadRibbonButton fieldButton = new RadRibbonButton()
{
Text = this.GetFriendlyFieldName(fieldName),
Size = ButtonSize.Medium,
HorizontalAlignment = HorizontalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Left
};
fieldButton.Command = this.editor.Commands.InsertFieldCommand;
fieldButton.CommandParameter = new MergeField() { PropertyPath = fieldName };
//or
//fieldButton.CommandParameter = new CustomMergeField() { PropertyPath = fieldName };
stackPanel.Children.Add(fieldButton);
}
stackPanel.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
scrollViewer.Content = stackPanel;
grid.Children.Add(scrollViewer);
radRibbonDropDownButton.DropDownContent = grid;
}
You can, of course optimize the code of the GetFriendlyName method and add it in a way that will be available by both classes.
Do anyone have good ideas of how to modify the toolbar for the WinForms version of the ReportViewer Toolbar?
That is, I want to remove some buttons and varius, but it looks like the solution is to create a brand new toolbar instead of modifying the one that is there.
Like, I had to remove export to excel, and did it this way:
// Disable excel export
foreach (RenderingExtension extension in lr.ListRenderingExtensions()) {
if (extension.Name == "Excel") {
//extension.Visible = false; // Property is readonly...
FieldInfo fi = extension.GetType().GetField("m_isVisible", BindingFlags.Instance | BindingFlags.NonPublic);
fi.SetValue(extension, false);
}
}
A bit trickysh if you ask me..
For removing toolbarbuttons, an possible way was to iterate through the Control array inside the ReportViewer and change the Visible property for the buttons to hide, but it gets reset all the time, so it is not an good way..
WHEN do MS come with an new version btw?
Yeap. You can do that in a little tricky way.
I had a task to add more scale factors to zoom report. I did it this way:
private readonly string[] ZOOM_VALUES = { "25%", "50%", "75%", "100%", "110%", "120%", "125%", "130%", "140%", "150%", "175%", "200%", "300%", "400%", "500%" };
private readonly int DEFAULT_ZOOM = 3;
//--
public ucReportViewer()
{
InitializeComponent();
this.reportViewer1.ProcessingMode = ProcessingMode.Local;
setScaleFactor(ZOOM_VALUES[DEFAULT_ZOOM]);
Control[] tb = reportViewer1.Controls.Find("ReportToolBar", true);
ToolStrip ts;
if (tb != null && tb.Length > 0 && tb[0].Controls.Count > 0 && (ts = tb[0].Controls[0] as ToolStrip) != null)
{
//here we go if our trick works (tested at .NET Framework 2.0.50727 SP1)
ToolStripComboBox tscb = new ToolStripComboBox();
tscb.DropDownStyle = ComboBoxStyle.DropDownList;
tscb.Items.AddRange(ZOOM_VALUES);
tscb.SelectedIndex = 3; //100%
tscb.SelectedIndexChanged += new EventHandler(toolStripZoomPercent_Click);
ts.Items.Add(tscb);
}
else
{
//if there is some problems - just use context menu
ContextMenuStrip cmZoomMenu = new ContextMenuStrip();
for (int i = 0; i < ZOOM_VALUES.Length; i++)
{
ToolStripMenuItem tsmi = new ToolStripMenuItem(ZOOM_VALUES[i]);
tsmi.Checked = (i == DEFAULT_ZOOM);
//tsmi.Tag = (IntPtr)cmZoomMenu;
tsmi.Click += new EventHandler(toolStripZoomPercent_Click);
cmZoomMenu.Items.Add(tsmi);
}
reportViewer1.ContextMenuStrip = cmZoomMenu;
}
}
private bool setScaleFactor(string value)
{
try
{
int percent = Convert.ToInt32(value.TrimEnd('%'));
reportViewer1.ZoomMode = ZoomMode.Percent;
reportViewer1.ZoomPercent = percent;
return true;
}
catch
{
return false;
}
}
private void toolStripZoomPercent_Click(object sender, EventArgs e)
{
ToolStripMenuItem tsmi = sender as ToolStripMenuItem;
ToolStripComboBox tscb = sender as ToolStripComboBox;
if (tscb != null && tscb.SelectedIndex > -1)
{
setScaleFactor(tscb.Items[tscb.SelectedIndex].ToString());
}
else if (tsmi != null)
{
if (setScaleFactor(tsmi.Text))
{
foreach (ToolStripItem tsi in tsmi.Owner.Items)
{
ToolStripMenuItem item = tsi as ToolStripMenuItem;
if (item != null && item.Checked)
{
item.Checked = false;
}
}
tsmi.Checked = true;
}
else
{
tsmi.Checked = false;
}
}
}
Get the toolbar from ReportViewer control:
ToolStrip toolStrip = (ToolStrip)reportViewer.Controls.Find("toolStrip1", true)[0]
Add new items:
toolStrip.Items.Add(...)
There are a lot of properties to set which buttons would you like to see.
For example ShowBackButton, ShowExportButton, ShowFindControls, and so on. Check them in the help, all starts with "Show".
But you are right, you cannot add new buttons. You have to create your own toolbar in order to do this.
What do you mean about new version? There is already a 2008 SP1 version of it.
Another way would be to manipulate the generated HTML at runtime via javascript. It's not very elegant, but it does give you full control over the generated HTML.
For VS2013 web ReportViewer V11 (indicated as rv), the code below adds a button.
private void AddPrintBtn()
{
foreach (Control c in rv.Controls)
{
foreach (Control c1 in c.Controls)
{
foreach (Control c2 in c1.Controls)
{
foreach (Control c3 in c2.Controls)
{
if (c3.ToString() == "Microsoft.Reporting.WebForms.ToolbarControl")
{
foreach (Control c4 in c3.Controls)
{
if (c4.ToString() == "Microsoft.Reporting.WebForms.PageNavigationGroup")
{
var btn = new Button();
btn.Text = "Criteria";
btn.ID = "btnFlip";
btn.OnClientClick = "$('#pnl').toggle();";
c4.Controls.Add(btn);
return;
}
}
}
}
}
}
}
}
I had this question for al ong time I I found the answer after a long tie and the main source of kowledge I used was this webpega: I'd like to thank you all guys adding the code that allowed me to do it and a picture with the result.
Instead of using the ReportViewer Class, you need to create a new classs, in my case, I named it ReportViewerPlus and it goes like this:
using Microsoft.Reporting.WinForms;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace X
{
class ReportViewerPlus : ReportViewer
{
private Button boton { get; set; }
public ReportViewerPlus(Button but) {
this.boton = but;
testc(this.Controls[0]);
}
public ReportViewerPlus()
{
}
private void testc(Control item){
if(item is ToolStrip)
{
ToolStripItemCollection tsic = ((ToolStrip)item).Items;
tsic.Insert(0, new ToolStripControlHost(boton));
return;
}
for (int i = 0; i < item.Controls.Count; i++)
{
testc(item.Controls[i]);
}
}
}
}
You have to add the button directly in the constructor of the class and you can configure the button in your designer.
Here's a pic of the result, not perfect, but enough to go(safe link I swear, but I can't post my own pics, don't have enough reputation).
http://prntscr.com/5lfssj
If you look carefully in the code of the class, you'd see more or less how it works and you could make your changes and make it possible to establish it in other site of the toolbar.
Thank you so much for helping me in the past, I hope this helps lots of people!
Generally you are suppose to create your own toolbar if you want to modify it. Your solution for removing buttons will probably work if that is all you need to do, but if you want to add your own you should probably just bite the bullet and build a replacement.
You may modify reportviewer controls by CustomizeReportToolStrip method.
this example remove Page Setup Button, Page Layout Button in WinForm
public CustOrderReportForm() {
InitializeComponent();
CustomizeReport(this.reportViewer1);
}
private void CustomizeReport(Control reportControl, int recurCount = 0) {
Console.WriteLine("".PadLeft(recurCount + 1, '.') + reportControl.GetType() + ":" + reportControl.Name);
if (reportControl is Button) {
CustomizeReportButton((Button)reportControl, recurCount);
}
else if (reportControl is ToolStrip) {
CustomizeReportToolStrip((ToolStrip)reportControl, recurCount);
}
foreach (Control childControl in reportControl.Controls) {
CustomizeReport(childControl, recurCount + 1);
}
}
//-------------------------------------------------------------
void CustomizeReportToolStrip(ToolStrip c, int recurCount) {
List<ToolStripItem> customized = new List<ToolStripItem>();
foreach (ToolStripItem i in c.Items) {
if (CustomizeReportToolStripItem(i, recurCount + 1)) {
customized.Add(i);
}
}
foreach (var i in customized) c.Items.Remove(i);
}
//-------------------------------------------------------------
void CustomizeReportButton(Button button, int recurCount) {
}
//-------------------------------------------------------------
bool CustomizeReportToolStripItem(ToolStripItem i, int recurCount) {
Console.WriteLine("".PadLeft(recurCount + 1, '.') + i.GetType() + ":" + i.Name);
if (i.Name == "pageSetup") {
return true;
}
else if (i.Name == "printPreview") {
return true;
}
return false; ;
}